unicorn 4.9.0 → 5.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,12 +4,6 @@ require 'socket'
4
4
 
5
5
  module Unicorn
6
6
  module SocketHelper
7
- # :stopdoc:
8
- include Socket::Constants
9
-
10
- # prevents IO objects in here from being GC-ed
11
- # kill this when we drop 1.8 support
12
- IO_PURGATORY = []
13
7
 
14
8
  # internal interface, only used by Rainbows!/Zbatery
15
9
  DEFAULTS = {
@@ -22,7 +16,7 @@ module Unicorn
22
16
  :tcp_defer_accept => 1,
23
17
 
24
18
  # FreeBSD, we need to override this to 'dataready' if we
25
- # eventually get HTTPS support
19
+ # eventually support non-HTTP/1.x
26
20
  :accept_filter => 'httpready',
27
21
 
28
22
  # same default value as Mongrel
@@ -32,76 +26,47 @@ module Unicorn
32
26
  :tcp_nopush => nil,
33
27
  :tcp_nodelay => true,
34
28
  }
35
- #:startdoc:
36
29
 
37
30
  # configure platform-specific options (only tested on Linux 2.6 so far)
38
- case RUBY_PLATFORM
39
- when /linux/
40
- # from /usr/include/linux/tcp.h
41
- TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT)
42
-
43
- # do not send out partial frames (Linux)
44
- TCP_CORK = 3 unless defined?(TCP_CORK)
45
-
46
- # Linux got SO_REUSEPORT in 3.9, BSDs have had it for ages
47
- unless defined?(SO_REUSEPORT)
48
- if RUBY_PLATFORM =~ /(?:alpha|mips|parisc|sparc)/
49
- SO_REUSEPORT = 0x0200 # untested
50
- else
51
- SO_REUSEPORT = 15 # only tested on x86_64 and i686
52
- end
53
- end
54
- when /freebsd/
55
- # do not send out partial frames (FreeBSD)
56
- TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
57
-
58
- def accf_arg(af_name)
59
- [ af_name, nil ].pack('a16a240')
60
- end if defined?(SO_ACCEPTFILTER)
61
- end
62
-
63
- def prevent_autoclose(io)
64
- if io.respond_to?(:autoclose=)
65
- io.autoclose = false
66
- else
67
- IO_PURGATORY << io
68
- end
69
- end
31
+ def accf_arg(af_name)
32
+ [ af_name, nil ].pack('a16a240')
33
+ end if RUBY_PLATFORM =~ /freebsd/ && Socket.const_defined?(:SO_ACCEPTFILTER)
70
34
 
71
35
  def set_tcp_sockopt(sock, opt)
72
36
  # just in case, even LANs can break sometimes. Linux sysadmins
73
37
  # can lower net.ipv4.tcp_keepalive_* sysctl knobs to very low values.
74
- sock.setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1) if defined?(SO_KEEPALIVE)
38
+ Socket.const_defined?(:SO_KEEPALIVE) and
39
+ sock.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 1)
75
40
 
76
- if defined?(TCP_NODELAY)
41
+ if Socket.const_defined?(:TCP_NODELAY)
77
42
  val = opt[:tcp_nodelay]
78
- val = DEFAULTS[:tcp_nodelay] if nil == val
79
- sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0)
43
+ val = DEFAULTS[:tcp_nodelay] if val.nil?
44
+ sock.setsockopt(:IPPROTO_TCP, :TCP_NODELAY, val ? 1 : 0)
80
45
  end
81
46
 
82
47
  val = opt[:tcp_nopush]
83
48
  unless val.nil?
84
- if defined?(TCP_CORK) # Linux
85
- sock.setsockopt(IPPROTO_TCP, TCP_CORK, val)
86
- elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is lightly tested (FreeBSD)
87
- sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val)
49
+ if Socket.const_defined?(:TCP_CORK) # Linux
50
+ sock.setsockopt(:IPPROTO_TCP, :TCP_CORK, val)
51
+ elsif Socket.const_defined?(:TCP_NOPUSH) # FreeBSD
52
+ sock.setsockopt(:IPPROTO_TCP, :TCP_NOPUSH, val)
88
53
  end
89
54
  end
90
55
 
91
- # No good reason to ever have deferred accepts off
92
- # (except maybe benchmarking)
93
- if defined?(TCP_DEFER_ACCEPT)
56
+ # No good reason to ever have deferred accepts off in single-threaded
57
+ # servers (except maybe benchmarking)
58
+ if Socket.const_defined?(:TCP_DEFER_ACCEPT)
94
59
  # this differs from nginx, since nginx doesn't allow us to
95
60
  # configure the the timeout...
96
61
  seconds = opt[:tcp_defer_accept]
97
62
  seconds = DEFAULTS[:tcp_defer_accept] if [true,nil].include?(seconds)
98
63
  seconds = 0 unless seconds # nil/false means disable this
99
- sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, seconds)
64
+ sock.setsockopt(:IPPROTO_TCP, :TCP_DEFER_ACCEPT, seconds)
100
65
  elsif respond_to?(:accf_arg)
101
66
  name = opt[:accept_filter]
102
- name = DEFAULTS[:accept_filter] if nil == name
67
+ name = DEFAULTS[:accept_filter] if name.nil?
103
68
  begin
104
- sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, accf_arg(name))
69
+ sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, accf_arg(name))
105
70
  rescue => e
106
71
  logger.error("#{sock_name(sock)} " \
107
72
  "failed to set accept_filter=#{name} (#{e.inspect})")
@@ -114,10 +79,11 @@ module Unicorn
114
79
 
115
80
  TCPSocket === sock and set_tcp_sockopt(sock, opt)
116
81
 
117
- if opt[:rcvbuf] || opt[:sndbuf]
82
+ rcvbuf, sndbuf = opt.values_at(:rcvbuf, :sndbuf)
83
+ if rcvbuf || sndbuf
118
84
  log_buffer_sizes(sock, "before: ")
119
- sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
120
- sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
85
+ sock.setsockopt(:SOL_SOCKET, :SO_RCVBUF, rcvbuf) if rcvbuf
86
+ sock.setsockopt(:SOL_SOCKET, :SO_SNDBUF, sndbuf) if sndbuf
121
87
  log_buffer_sizes(sock, " after: ")
122
88
  end
123
89
  sock.listen(opt[:backlog])
@@ -126,8 +92,8 @@ module Unicorn
126
92
  end
127
93
 
128
94
  def log_buffer_sizes(sock, pfx = '')
129
- rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
130
- sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
95
+ rcvbuf = sock.getsockopt(:SOL_SOCKET, :SO_RCVBUF).int
96
+ sndbuf = sock.getsockopt(:SOL_SOCKET, :SO_SNDBUF).int
131
97
  logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
132
98
  end
133
99
 
@@ -172,25 +138,25 @@ module Unicorn
172
138
 
173
139
  def new_tcp_server(addr, port, opt)
174
140
  # n.b. we set FD_CLOEXEC in the workers
175
- sock = Socket.new(opt[:ipv6] ? AF_INET6 : AF_INET, SOCK_STREAM, 0)
141
+ sock = Socket.new(opt[:ipv6] ? :AF_INET6 : :AF_INET, :SOCK_STREAM)
176
142
  if opt.key?(:ipv6only)
177
- defined?(IPV6_V6ONLY) or
143
+ Socket.const_defined?(:IPV6_V6ONLY) or
178
144
  abort "Socket::IPV6_V6ONLY not defined, upgrade Ruby and/or your OS"
179
- sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
145
+ sock.setsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
180
146
  end
181
- sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
182
- if defined?(SO_REUSEPORT) && opt[:reuseport]
183
- sock.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
147
+ sock.setsockopt(:SOL_SOCKET, :SO_REUSEADDR, 1)
148
+ if Socket.const_defined?(:SO_REUSEPORT) && opt[:reuseport]
149
+ sock.setsockopt(:SOL_SOCKET, :SO_REUSEPORT, 1)
184
150
  end
185
151
  sock.bind(Socket.pack_sockaddr_in(port, addr))
186
- prevent_autoclose(sock)
152
+ sock.autoclose = false
187
153
  Kgio::TCPServer.for_fd(sock.fileno)
188
154
  end
189
155
 
190
156
  # returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
191
157
  def tcp_name(sock)
192
158
  port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
193
- /:/ =~ addr ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
159
+ addr.include?(':') ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
194
160
  end
195
161
  module_function :tcp_name
196
162
 
@@ -22,11 +22,6 @@ class Unicorn::TmpIO < File
22
22
  fp
23
23
  end
24
24
 
25
- # for easier env["rack.input"] compatibility with Rack <= 1.1
26
- def size
27
- stat.size
28
- end unless File.method_defined?(:size)
29
-
30
25
  # pretend we're Tempfile for Rack::TempfileReaper
31
26
  alias close! close
32
27
  end
@@ -1,5 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
+ require 'fcntl'
3
4
  module Unicorn::Util
4
5
 
5
6
  # :stopdoc:
@@ -11,7 +11,6 @@ require "raindrops"
11
11
  class Unicorn::Worker
12
12
  # :stopdoc:
13
13
  attr_accessor :nr, :switched
14
- attr_writer :tmp
15
14
  attr_reader :to_io # IO.select-compatible
16
15
 
17
16
  PER_DROP = Raindrops::PAGE_SIZE / Raindrops::SIZE
@@ -23,7 +22,7 @@ class Unicorn::Worker
23
22
  @offset = nr % PER_DROP
24
23
  @raindrop[@offset] = 0
25
24
  @nr = nr
26
- @tmp = @switched = false
25
+ @switched = false
27
26
  @to_io, @master = Unicorn.pipe
28
27
  end
29
28
 
@@ -101,18 +100,8 @@ class Unicorn::Worker
101
100
  @raindrop[@offset]
102
101
  end
103
102
 
104
- # only exists for compatibility
105
- def tmp # :nodoc:
106
- @tmp ||= begin
107
- tmp = Unicorn::TmpIO.new
108
- tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
109
- tmp
110
- end
111
- end
112
-
113
103
  # called in both the master (reaping worker) and worker (SIGQUIT handler)
114
104
  def close # :nodoc:
115
- @tmp.close if @tmp
116
105
  @master.close if @master
117
106
  @to_io.close if @to_io
118
107
  end
@@ -141,7 +130,6 @@ class Unicorn::Worker
141
130
  uid = Etc.getpwnam(user).uid
142
131
  gid = Etc.getgrnam(group).gid if group
143
132
  Unicorn::Util.chown_logs(uid, gid)
144
- @tmp.chown(uid, gid) if @tmp
145
133
  if gid && Process.egid != gid
146
134
  Process.initgroups(user, gid)
147
135
  Process::GID.change_privilege(gid)
@@ -2,12 +2,13 @@ use Rack::Lint
2
2
  use Rack::ContentLength
3
3
  use Rack::ContentType, "text/plain"
4
4
  class DieIfUsed
5
+ @@n = 0
5
6
  def each
6
7
  abort "body.each called after response hijack\n"
7
8
  end
8
9
 
9
10
  def close
10
- abort "body.close called after response hijack\n"
11
+ warn "closed DieIfUsed #{@@n += 1}\n"
11
12
  end
12
13
  end
13
14
  run lambda { |env|
@@ -16,12 +16,15 @@ t_begin "check response hijack" && {
16
16
  test "xresponse.hijacked" = x"$(curl -sSfv http://$listen/hijack_res)"
17
17
  }
18
18
 
19
- t_begin "killing succeeds" && {
19
+ t_begin "killing succeeds after hijack" && {
20
20
  kill $unicorn_pid
21
21
  }
22
22
 
23
- t_begin "check stderr" && {
23
+ t_begin "check stderr for hijacked body close" && {
24
24
  check_stderr
25
+ grep 'closed DieIfUsed 1\>' $r_err
26
+ grep 'closed DieIfUsed 2\>' $r_err
27
+ ! grep 'closed DieIfUsed 3\>' $r_err
25
28
  }
26
29
 
27
30
  t_done
@@ -292,6 +292,7 @@ def chunked_spawn(stdout, *cmd)
292
292
  end
293
293
 
294
294
  def reset_sig_handlers
295
- sigs = %w(CHLD).concat(Unicorn::HttpServer::QUEUE_SIGS)
296
- sigs.each { |sig| trap(sig, "DEFAULT") }
295
+ %w(WINCH QUIT INT TERM USR1 USR2 HUP TTIN TTOU CHLD).each do |sig|
296
+ trap(sig, "DEFAULT")
297
+ end
297
298
  end
@@ -8,10 +8,15 @@ include Unicorn
8
8
  class HttpParserNgTest < Test::Unit::TestCase
9
9
 
10
10
  def setup
11
- HttpParser.keepalive_requests = HttpParser::KEEPALIVE_REQUESTS_DEFAULT
12
11
  @parser = HttpParser.new
13
12
  end
14
13
 
14
+ def test_parser_max_len
15
+ assert_raises(RangeError) do
16
+ HttpParser.max_header_len = 0xffffffff + 1
17
+ end
18
+ end
19
+
15
20
  def test_next_clear
16
21
  r = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
17
22
  @parser.buf << r
@@ -29,23 +34,15 @@ class HttpParserNgTest < Test::Unit::TestCase
29
34
  assert_equal false, @parser.response_start_sent
30
35
  end
31
36
 
32
- def test_keepalive_requests_default_constant
33
- assert_kind_of Integer, HttpParser::KEEPALIVE_REQUESTS_DEFAULT
34
- assert HttpParser::KEEPALIVE_REQUESTS_DEFAULT >= 0
35
- end
36
-
37
- def test_keepalive_requests_setting
38
- HttpParser.keepalive_requests = 0
39
- assert_equal 0, HttpParser.keepalive_requests
40
- HttpParser.keepalive_requests = nil
41
- assert HttpParser.keepalive_requests >= 0xffffffff
42
- HttpParser.keepalive_requests = 1
43
- assert_equal 1, HttpParser.keepalive_requests
44
- HttpParser.keepalive_requests = 666
45
- assert_equal 666, HttpParser.keepalive_requests
46
-
47
- assert_raises(TypeError) { HttpParser.keepalive_requests = "666" }
48
- assert_raises(TypeError) { HttpParser.keepalive_requests = [] }
37
+ def test_response_start_sent
38
+ assert_equal false, @parser.response_start_sent, "default is false"
39
+ @parser.response_start_sent = true
40
+ assert_equal true, @parser.response_start_sent
41
+ @parser.response_start_sent = false
42
+ assert_equal false, @parser.response_start_sent
43
+ @parser.response_start_sent = true
44
+ @parser.clear
45
+ assert_equal false, @parser.response_start_sent
49
46
  end
50
47
 
51
48
  def test_connection_TE
@@ -71,41 +68,11 @@ class HttpParserNgTest < Test::Unit::TestCase
71
68
  "REQUEST_METHOD" => "GET",
72
69
  "QUERY_STRING" => ""
73
70
  }.freeze
74
- HttpParser::KEEPALIVE_REQUESTS_DEFAULT.times do |nr|
71
+ 100.times do |nr|
75
72
  @parser.buf << req
76
73
  assert_equal expect, @parser.parse
77
74
  assert @parser.next?
78
75
  end
79
- @parser.buf << req
80
- assert_equal expect, @parser.parse
81
- assert ! @parser.next?
82
- end
83
-
84
- def test_fewer_keepalive_requests_with_next?
85
- HttpParser.keepalive_requests = 5
86
- @parser = HttpParser.new
87
- req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
88
- expect = {
89
- "SERVER_NAME" => "example.com",
90
- "HTTP_HOST" => "example.com",
91
- "rack.url_scheme" => "http",
92
- "REQUEST_PATH" => "/",
93
- "SERVER_PROTOCOL" => "HTTP/1.1",
94
- "PATH_INFO" => "/",
95
- "HTTP_VERSION" => "HTTP/1.1",
96
- "REQUEST_URI" => "/",
97
- "SERVER_PORT" => "80",
98
- "REQUEST_METHOD" => "GET",
99
- "QUERY_STRING" => ""
100
- }.freeze
101
- 5.times do |nr|
102
- @parser.buf << req
103
- assert_equal expect, @parser.parse
104
- assert @parser.next?
105
- end
106
- @parser.buf << req
107
- assert_equal expect, @parser.parse
108
- assert ! @parser.next?
109
76
  end
110
77
 
111
78
  def test_default_keepalive_is_off
@@ -663,69 +630,4 @@ class HttpParserNgTest < Test::Unit::TestCase
663
630
  assert_equal expect, env2
664
631
  assert_equal "", @parser.buf
665
632
  end
666
-
667
- def test_keepalive_requests_disabled
668
- req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
669
- expect = {
670
- "SERVER_NAME" => "example.com",
671
- "HTTP_HOST" => "example.com",
672
- "rack.url_scheme" => "http",
673
- "REQUEST_PATH" => "/",
674
- "SERVER_PROTOCOL" => "HTTP/1.1",
675
- "PATH_INFO" => "/",
676
- "HTTP_VERSION" => "HTTP/1.1",
677
- "REQUEST_URI" => "/",
678
- "SERVER_PORT" => "80",
679
- "REQUEST_METHOD" => "GET",
680
- "QUERY_STRING" => ""
681
- }.freeze
682
- HttpParser.keepalive_requests = 0
683
- @parser = HttpParser.new
684
- @parser.buf << req
685
- assert_equal expect, @parser.parse
686
- assert ! @parser.next?
687
- end
688
-
689
- def test_chunk_only
690
- tmp = ""
691
- assert_equal @parser, @parser.dechunk!
692
- assert_nil @parser.filter_body(tmp, "6\r\n")
693
- assert_equal "", tmp
694
- assert_nil @parser.filter_body(tmp, "abcdef")
695
- assert_equal "abcdef", tmp
696
- assert_nil @parser.filter_body(tmp, "\r\n")
697
- assert_equal "", tmp
698
- src = "0\r\n\r\n"
699
- assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
700
- assert_equal "", tmp
701
- end
702
-
703
- def test_chunk_only_bad_align
704
- tmp = ""
705
- assert_equal @parser, @parser.dechunk!
706
- assert_nil @parser.filter_body(tmp, "6\r\na")
707
- assert_equal "a", tmp
708
- assert_nil @parser.filter_body(tmp, "bcde")
709
- assert_equal "bcde", tmp
710
- assert_nil @parser.filter_body(tmp, "f\r")
711
- assert_equal "f", tmp
712
- src = "\n0\r\n\r\n"
713
- assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
714
- assert_equal "", tmp
715
- end
716
-
717
- def test_chunk_only_reset_ok
718
- tmp = ""
719
- assert_equal @parser, @parser.dechunk!
720
- src = "1\r\na\r\n0\r\n\r\n"
721
- assert_nil @parser.filter_body(tmp, src)
722
- assert_equal "a", tmp
723
- assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
724
-
725
- assert_equal @parser, @parser.dechunk!
726
- src = "0\r\n\r\n"
727
- assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
728
- assert_equal "", tmp
729
- assert_equal src, @parser.filter_body(tmp, src)
730
- end
731
633
  end
@@ -38,7 +38,6 @@ class ResponseTest < Test::Unit::TestCase
38
38
  http_response_write(out,'200', {}, [])
39
39
  assert ! out.closed?
40
40
  assert out.length > 0, "output didn't have data"
41
- assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/).size
42
41
  end
43
42
 
44
43
  def test_response_200
@@ -71,18 +70,6 @@ class ResponseTest < Test::Unit::TestCase
71
70
  out = StringIO.new
72
71
  http_response_write(out,200, {"X-Whatever" => "stuff"}, [])
73
72
  assert ! out.closed?
74
- assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/i).size
75
- end
76
-
77
- def test_body_closed
78
- expect_body = %w(1 2 3 4).join("\n")
79
- body = StringIO.new(expect_body)
80
- body.rewind
81
- out = StringIO.new
82
- http_response_write(out,200, {}, body)
83
- assert ! out.closed?
84
- assert body.closed?
85
- assert_match(expect_body, out.string.split(/\r\n/).last)
86
73
  end
87
74
 
88
75
  def test_unknown_status_pass_through
@@ -91,9 +78,5 @@ class ResponseTest < Test::Unit::TestCase
91
78
  assert ! out.closed?
92
79
  headers = out.string.split(/\r\n\r\n/).first.split(/\r\n/)
93
80
  assert %r{\AHTTP/\d\.\d 666 I AM THE BEAST\z}.match(headers[0])
94
- status = headers.grep(/\AStatus:/i).first
95
- assert status
96
- assert_equal "Status: 666 I AM THE BEAST", status
97
81
  end
98
-
99
82
  end