unicorn 4.7.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/.document +0 -1
  3. data/.gitattributes +5 -0
  4. data/.gitignore +2 -2
  5. data/.manifest +14 -21
  6. data/.olddoc.yml +22 -0
  7. data/Application_Timeouts +7 -7
  8. data/DESIGN +2 -4
  9. data/Documentation/.gitignore +1 -3
  10. data/Documentation/unicorn.1 +222 -0
  11. data/Documentation/unicorn_rails.1 +207 -0
  12. data/FAQ +23 -6
  13. data/GIT-VERSION-FILE +1 -1
  14. data/GIT-VERSION-GEN +1 -1
  15. data/GNUmakefile +139 -92
  16. data/HACKING +13 -28
  17. data/ISSUES +82 -19
  18. data/KNOWN_ISSUES +18 -18
  19. data/LATEST +22 -44
  20. data/LICENSE +2 -2
  21. data/Links +24 -22
  22. data/NEWS +729 -0
  23. data/PHILOSOPHY +0 -6
  24. data/README +50 -48
  25. data/Rakefile +0 -44
  26. data/SIGNALS +12 -3
  27. data/Sandbox +11 -10
  28. data/TODO +0 -2
  29. data/TUNING +30 -9
  30. data/archive/.gitignore +3 -0
  31. data/archive/slrnpull.conf +4 -0
  32. data/bin/unicorn +4 -2
  33. data/bin/unicorn_rails +3 -3
  34. data/examples/big_app_gc.rb +1 -1
  35. data/examples/init.sh +36 -8
  36. data/examples/logrotate.conf +17 -2
  37. data/examples/nginx.conf +14 -14
  38. data/examples/unicorn.conf.minimal.rb +2 -2
  39. data/examples/unicorn.conf.rb +14 -6
  40. data/examples/unicorn.socket +11 -0
  41. data/examples/unicorn@.service +40 -0
  42. data/ext/unicorn_http/common_field_optimization.h +23 -5
  43. data/ext/unicorn_http/ext_help.h +0 -20
  44. data/ext/unicorn_http/extconf.rb +37 -1
  45. data/ext/unicorn_http/global_variables.h +1 -1
  46. data/ext/unicorn_http/httpdate.c +2 -2
  47. data/ext/unicorn_http/unicorn_http.c +940 -644
  48. data/ext/unicorn_http/unicorn_http.rl +167 -170
  49. data/ext/unicorn_http/unicorn_http_common.rl +1 -1
  50. data/lib/unicorn/configurator.rb +110 -46
  51. data/lib/unicorn/const.rb +2 -25
  52. data/lib/unicorn/http_request.rb +110 -31
  53. data/lib/unicorn/http_response.rb +17 -31
  54. data/lib/unicorn/http_server.rb +292 -199
  55. data/lib/unicorn/launcher.rb +1 -1
  56. data/lib/unicorn/oob_gc.rb +16 -6
  57. data/lib/unicorn/socket_helper.rb +58 -78
  58. data/lib/unicorn/stream_input.rb +9 -11
  59. data/lib/unicorn/tee_input.rb +16 -11
  60. data/lib/unicorn/tmpio.rb +10 -6
  61. data/lib/unicorn/util.rb +5 -4
  62. data/lib/unicorn/version.rb +1 -1
  63. data/lib/unicorn/worker.rb +99 -22
  64. data/lib/unicorn.rb +69 -42
  65. data/man/man1/unicorn.1 +124 -122
  66. data/man/man1/unicorn_rails.1 +113 -127
  67. data/t/.gitignore +0 -1
  68. data/t/GNUmakefile +3 -80
  69. data/t/README +4 -4
  70. data/t/t0002-parser-error.sh +3 -3
  71. data/t/t0011-active-unix-socket.sh +1 -1
  72. data/t/t0012-reload-empty-config.sh +2 -1
  73. data/t/t0300-no-default-middleware.sh +6 -1
  74. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  75. data/t/t0301.ru +13 -0
  76. data/t/test-lib.sh +2 -2
  77. data/test/benchmark/README +14 -4
  78. data/test/benchmark/ddstream.ru +50 -0
  79. data/test/benchmark/readinput.ru +40 -0
  80. data/test/benchmark/uconnect.perl +66 -0
  81. data/test/exec/test_exec.rb +74 -20
  82. data/test/test_helper.rb +42 -33
  83. data/test/unit/test_ccc.rb +91 -0
  84. data/test/unit/test_droplet.rb +1 -1
  85. data/test/unit/test_http_parser.rb +49 -19
  86. data/test/unit/test_http_parser_ng.rb +98 -115
  87. data/test/unit/test_request.rb +11 -11
  88. data/test/unit/test_response.rb +31 -19
  89. data/test/unit/test_server.rb +89 -15
  90. data/test/unit/test_signals.rb +9 -9
  91. data/test/unit/test_socket_helper.rb +20 -14
  92. data/test/unit/test_tee_input.rb +10 -0
  93. data/test/unit/test_upload.rb +10 -15
  94. data/test/unit/test_util.rb +28 -3
  95. data/unicorn.gemspec +28 -23
  96. data/unicorn_1 +1 -0
  97. data/unicorn_rails_1 +1 -0
  98. metadata +64 -134
  99. data/.wrongdoc.yml +0 -10
  100. data/ChangeLog +0 -4694
  101. data/Documentation/GNUmakefile +0 -30
  102. data/Documentation/unicorn.1.txt +0 -178
  103. data/Documentation/unicorn_rails.1.txt +0 -175
  104. data/examples/git.ru +0 -13
  105. data/lib/unicorn/app/exec_cgi.rb +0 -154
  106. data/lib/unicorn/app/inetd.rb +0 -109
  107. data/lib/unicorn/ssl_client.rb +0 -11
  108. data/lib/unicorn/ssl_configurator.rb +0 -104
  109. data/lib/unicorn/ssl_server.rb +0 -42
  110. data/local.mk.sample +0 -59
  111. data/script/isolate_for_tests +0 -32
  112. data/t/hijack.ru +0 -42
  113. data/t/sslgen.sh +0 -71
  114. data/t/t0016-trust-x-forwarded-false.sh +0 -30
  115. data/t/t0017-trust-x-forwarded-true.sh +0 -30
  116. data/t/t0200-rack-hijack.sh +0 -27
  117. data/t/t0600-https-server-basic.sh +0 -48
  118. data/test/unit/test_http_parser_xftrust.rb +0 -38
  119. data/test/unit/test_sni_hostnames.rb +0 -47
@@ -31,7 +31,7 @@ module Unicorn::Launcher
31
31
  # \_ parent - exits immediately ASAP
32
32
  # \_ unicorn master - writes to pipe when ready
33
33
 
34
- rd, wr = IO.pipe
34
+ rd, wr = Unicorn.pipe
35
35
  grandparent = $$
36
36
  if fork
37
37
  wr.close # grandparent does not write
@@ -1,5 +1,15 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
+ # Strongly consider https://github.com/tmm1/gctools if using Ruby 2.1+
4
+ # It is built on new APIs in Ruby 2.1, so it is more intelligent than
5
+ # this historical implementation.
6
+ #
7
+ # Users on Ruby 2.0 (not 2.1+) may also want to check out
8
+ # lib/middleware/unicorn_oobgc.rb from the Discourse project
9
+ # (https://github.com/discourse/discourse)
10
+ #
11
+ # The following information is only for historical versions of Ruby.
12
+ #
3
13
  # Runs GC after requests, after closing the client socket and
4
14
  # before attempting to accept more connections.
5
15
  #
@@ -33,8 +43,9 @@
33
43
  # use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
34
44
  #
35
45
  # Feedback from users of early implementations of this module:
36
- # * http://comments.gmane.org/gmane.comp.lang.ruby.unicorn.general/486
37
- # * http://article.gmane.org/gmane.comp.lang.ruby.unicorn.general/596
46
+ # * https://yhbt.net/unicorn-public/0BFC98E9-072B-47EE-9A70-05478C20141B@lukemelia.com/
47
+ # * https://yhbt.net/unicorn-public/AANLkTilUbgdyDv9W1bi-s_W6kq9sOhWfmuYkKLoKGOLj@mail.gmail.com/
48
+
38
49
  module Unicorn::OobGC
39
50
 
40
51
  # this pretends to be Rack middleware because it used to be
@@ -49,18 +60,17 @@ module Unicorn::OobGC
49
60
  self.const_set :OOBGC_INTERVAL, interval
50
61
  ObjectSpace.each_object(Unicorn::HttpServer) do |s|
51
62
  s.extend(self)
52
- self.const_set :OOBGC_ENV, s.instance_variable_get(:@request).env
53
63
  end
54
64
  app # pretend to be Rack middleware since it was in the past
55
65
  end
56
66
 
57
67
  #:stopdoc:
58
- PATH_INFO = "PATH_INFO"
59
68
  def process_client(client)
60
69
  super(client) # Unicorn::HttpServer#process_client
61
- if OOBGC_PATH =~ OOBGC_ENV[PATH_INFO] && ((@@nr -= 1) <= 0)
70
+ env = instance_variable_get(:@request).env
71
+ if OOBGC_PATH =~ env['PATH_INFO'] && ((@@nr -= 1) <= 0)
62
72
  @@nr = OOBGC_INTERVAL
63
- OOBGC_ENV.clear
73
+ env.clear
64
74
  disabled = GC.enable
65
75
  GC.start
66
76
  GC.disable if disabled
@@ -3,26 +3,30 @@
3
3
  require 'socket'
4
4
 
5
5
  module Unicorn
6
- module SocketHelper
7
- # :stopdoc:
8
- include Socket::Constants
9
6
 
10
- # prevents IO objects in here from being GC-ed
11
- # kill this when we drop 1.8 support
12
- IO_PURGATORY = []
7
+ # Instead of using a generic Kgio::Socket for everything,
8
+ # tag TCP sockets so we can use TCP_INFO under Linux without
9
+ # incurring extra syscalls for Unix domain sockets.
10
+ # TODO: remove these when we remove kgio
11
+ TCPClient = Class.new(Kgio::Socket) # :nodoc:
12
+ class TCPSrv < Kgio::TCPServer # :nodoc:
13
+ def kgio_tryaccept # :nodoc:
14
+ super(TCPClient)
15
+ end
16
+ end
17
+
18
+ module SocketHelper
13
19
 
14
- # internal interface, only used by Rainbows!/Zbatery
20
+ # internal interface
15
21
  DEFAULTS = {
16
22
  # The semantics for TCP_DEFER_ACCEPT changed in Linux 2.6.32+
17
23
  # with commit d1b99ba41d6c5aa1ed2fc634323449dd656899e9
18
- # This change shouldn't affect Unicorn users behind nginx (a
19
- # value of 1 remains an optimization), but Rainbows! users may
20
- # want to use a higher value on Linux 2.6.32+ to protect against
21
- # denial-of-service attacks
24
+ # This change shouldn't affect unicorn users behind nginx (a
25
+ # value of 1 remains an optimization).
22
26
  :tcp_defer_accept => 1,
23
27
 
24
28
  # FreeBSD, we need to override this to 'dataready' if we
25
- # eventually get HTTPS support
29
+ # eventually support non-HTTP/1.x
26
30
  :accept_filter => 'httpready',
27
31
 
28
32
  # same default value as Mongrel
@@ -32,80 +36,55 @@ module Unicorn
32
36
  :tcp_nopush => nil,
33
37
  :tcp_nodelay => true,
34
38
  }
35
- #:startdoc:
36
39
 
37
40
  # 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
41
+ def accf_arg(af_name)
42
+ [ af_name, nil ].pack('a16a240')
43
+ end if RUBY_PLATFORM =~ /freebsd/ && Socket.const_defined?(:SO_ACCEPTFILTER)
70
44
 
71
45
  def set_tcp_sockopt(sock, opt)
72
46
  # just in case, even LANs can break sometimes. Linux sysadmins
73
47
  # can lower net.ipv4.tcp_keepalive_* sysctl knobs to very low values.
74
- sock.setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1) if defined?(SO_KEEPALIVE)
48
+ Socket.const_defined?(:SO_KEEPALIVE) and
49
+ sock.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 1)
75
50
 
76
- if defined?(TCP_NODELAY)
51
+ if Socket.const_defined?(:TCP_NODELAY)
77
52
  val = opt[:tcp_nodelay]
78
- val = DEFAULTS[:tcp_nodelay] if nil == val
79
- sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0)
53
+ val = DEFAULTS[:tcp_nodelay] if val.nil?
54
+ sock.setsockopt(:IPPROTO_TCP, :TCP_NODELAY, val ? 1 : 0)
80
55
  end
81
56
 
82
57
  val = opt[:tcp_nopush]
83
58
  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)
59
+ if Socket.const_defined?(:TCP_CORK) # Linux
60
+ sock.setsockopt(:IPPROTO_TCP, :TCP_CORK, val)
61
+ elsif Socket.const_defined?(:TCP_NOPUSH) # FreeBSD
62
+ sock.setsockopt(:IPPROTO_TCP, :TCP_NOPUSH, val)
88
63
  end
89
64
  end
90
65
 
91
- # No good reason to ever have deferred accepts off
92
- # (except maybe benchmarking)
93
- if defined?(TCP_DEFER_ACCEPT)
66
+ # No good reason to ever have deferred accepts off in single-threaded
67
+ # servers (except maybe benchmarking)
68
+ if Socket.const_defined?(:TCP_DEFER_ACCEPT)
94
69
  # this differs from nginx, since nginx doesn't allow us to
95
70
  # configure the the timeout...
96
71
  seconds = opt[:tcp_defer_accept]
97
72
  seconds = DEFAULTS[:tcp_defer_accept] if [true,nil].include?(seconds)
98
73
  seconds = 0 unless seconds # nil/false means disable this
99
- sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, seconds)
74
+ sock.setsockopt(:IPPROTO_TCP, :TCP_DEFER_ACCEPT, seconds)
100
75
  elsif respond_to?(:accf_arg)
101
76
  name = opt[:accept_filter]
102
- name = DEFAULTS[:accept_filter] if nil == name
77
+ name = DEFAULTS[:accept_filter] if name.nil?
78
+ sock.listen(opt[:backlog])
79
+ got = (sock.getsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER) rescue nil).to_s
80
+ arg = accf_arg(name)
103
81
  begin
104
- sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, accf_arg(name))
82
+ sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, arg)
105
83
  rescue => e
106
84
  logger.error("#{sock_name(sock)} " \
107
85
  "failed to set accept_filter=#{name} (#{e.inspect})")
108
- end
86
+ logger.error("perhaps accf_http(9) needs to be loaded".freeze)
87
+ end if arg != got
109
88
  end
110
89
  end
111
90
 
@@ -114,20 +93,21 @@ module Unicorn
114
93
 
115
94
  TCPSocket === sock and set_tcp_sockopt(sock, opt)
116
95
 
117
- if opt[:rcvbuf] || opt[:sndbuf]
96
+ rcvbuf, sndbuf = opt.values_at(:rcvbuf, :sndbuf)
97
+ if rcvbuf || sndbuf
118
98
  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]
99
+ sock.setsockopt(:SOL_SOCKET, :SO_RCVBUF, rcvbuf) if rcvbuf
100
+ sock.setsockopt(:SOL_SOCKET, :SO_SNDBUF, sndbuf) if sndbuf
121
101
  log_buffer_sizes(sock, " after: ")
122
102
  end
123
103
  sock.listen(opt[:backlog])
124
- rescue => e
125
- Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
104
+ rescue => e
105
+ Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
126
106
  end
127
107
 
128
108
  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')
109
+ rcvbuf = sock.getsockopt(:SOL_SOCKET, :SO_RCVBUF).int
110
+ sndbuf = sock.getsockopt(:SOL_SOCKET, :SO_SNDBUF).int
131
111
  logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
132
112
  end
133
113
 
@@ -137,7 +117,7 @@ module Unicorn
137
117
  def bind_listen(address = '0.0.0.0:8080', opt = {})
138
118
  return address unless String === address
139
119
 
140
- sock = if address[0] == ?/
120
+ sock = if address.start_with?('/')
141
121
  if File.exist?(address)
142
122
  if File.socket?(address)
143
123
  begin
@@ -172,25 +152,25 @@ module Unicorn
172
152
 
173
153
  def new_tcp_server(addr, port, opt)
174
154
  # n.b. we set FD_CLOEXEC in the workers
175
- sock = Socket.new(opt[:ipv6] ? AF_INET6 : AF_INET, SOCK_STREAM, 0)
155
+ sock = Socket.new(opt[:ipv6] ? :AF_INET6 : :AF_INET, :SOCK_STREAM)
176
156
  if opt.key?(:ipv6only)
177
- defined?(IPV6_V6ONLY) or
157
+ Socket.const_defined?(:IPV6_V6ONLY) or
178
158
  abort "Socket::IPV6_V6ONLY not defined, upgrade Ruby and/or your OS"
179
- sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
159
+ sock.setsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
180
160
  end
181
- sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
182
- if defined?(SO_REUSEPORT) && opt[:reuseport]
183
- sock.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
161
+ sock.setsockopt(:SOL_SOCKET, :SO_REUSEADDR, 1)
162
+ if Socket.const_defined?(:SO_REUSEPORT) && opt[:reuseport]
163
+ sock.setsockopt(:SOL_SOCKET, :SO_REUSEPORT, 1)
184
164
  end
185
165
  sock.bind(Socket.pack_sockaddr_in(port, addr))
186
- prevent_autoclose(sock)
187
- Kgio::TCPServer.for_fd(sock.fileno)
166
+ sock.autoclose = false
167
+ TCPSrv.for_fd(sock.fileno)
188
168
  end
189
169
 
190
170
  # returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
191
171
  def tcp_name(sock)
192
172
  port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
193
- /:/ =~ addr ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
173
+ addr.include?(':') ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
194
174
  end
195
175
  module_function :tcp_name
196
176
 
@@ -221,7 +201,7 @@ module Unicorn
221
201
  def server_cast(sock)
222
202
  begin
223
203
  Socket.unpack_sockaddr_in(sock.getsockname)
224
- Kgio::TCPServer.for_fd(sock.fileno)
204
+ TCPSrv.for_fd(sock.fileno)
225
205
  rescue ArgumentError
226
206
  Kgio::UNIXServer.for_fd(sock.fileno)
227
207
  end
@@ -1,16 +1,17 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- # When processing uploads, Unicorn may expose a StreamInput object under
4
- # "rack.input" of the (future) Rack (2.x) environment.
3
+ # When processing uploads, unicorn may expose a StreamInput object under
4
+ # "rack.input" of the Rack environment when
5
+ # Unicorn::Configurator#rewindable_input is set to +false+
5
6
  class Unicorn::StreamInput
6
7
  # The I/O chunk size (in +bytes+) for I/O operations where
7
8
  # the size cannot be user-specified when a method is called.
8
9
  # The default is 16 kilobytes.
9
- @@io_chunk_size = Unicorn::Const::CHUNK_SIZE
10
+ @@io_chunk_size = Unicorn::Const::CHUNK_SIZE # :nodoc:
10
11
 
11
12
  # Initializes a new StreamInput object. You normally do not have to call
12
13
  # this unless you are writing an HTTP server.
13
- def initialize(socket, request)
14
+ def initialize(socket, request) # :nodoc:
14
15
  @chunked = request.content_length.nil?
15
16
  @socket = socket
16
17
  @parser = request
@@ -53,7 +54,7 @@ class Unicorn::StreamInput
53
54
  rv << @rbuf
54
55
  to_read -= @rbuf.size
55
56
  end
56
- @rbuf.replace('')
57
+ @rbuf.clear
57
58
  end
58
59
  rv = nil if rv.empty? && length != 0
59
60
  else
@@ -130,8 +131,8 @@ private
130
131
  filter_body(@rbuf, @buf)
131
132
  dst << @rbuf
132
133
  end
133
- ensure
134
- @rbuf.replace('')
134
+ ensure
135
+ @rbuf.clear
135
136
  end
136
137
 
137
138
  def eof!
@@ -139,10 +140,7 @@ private
139
140
  # we do support clients that shutdown(SHUT_WR) after the
140
141
  # _entire_ request has been sent, and those will not have
141
142
  # raised EOFError on us.
142
- if @socket
143
- @socket.shutdown
144
- @socket.close
145
- end
143
+ @socket.shutdown if @socket
146
144
  ensure
147
145
  raise Unicorn::ClientShutdown, "bytes_read=#{@bytes_read}", []
148
146
  end
@@ -1,6 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- # acts like tee(1) on an input input to provide a input-like stream
3
+ # Acts like tee(1) on an input input to provide a input-like stream
4
4
  # while providing rewindable semantics through a File/StringIO backing
5
5
  # store. On the first pass, the input is only read on demand so your
6
6
  # Rack application can use input notification (upload progress and
@@ -9,32 +9,39 @@
9
9
  # strict interpretation of Rack::Lint::InputWrapper functionality and
10
10
  # will not support any deviations from it.
11
11
  #
12
- # When processing uploads, Unicorn exposes a TeeInput object under
13
- # "rack.input" of the Rack environment.
12
+ # When processing uploads, unicorn exposes a TeeInput object under
13
+ # "rack.input" of the Rack environment by default.
14
14
  class Unicorn::TeeInput < Unicorn::StreamInput
15
15
  # The maximum size (in +bytes+) to buffer in memory before
16
16
  # resorting to a temporary file. Default is 112 kilobytes.
17
- @@client_body_buffer_size = Unicorn::Const::MAX_BODY
17
+ @@client_body_buffer_size = Unicorn::Const::MAX_BODY # :nodoc:
18
18
 
19
19
  # sets the maximum size of request bodies to buffer in memory,
20
20
  # amounts larger than this are buffered to the filesystem
21
- def self.client_body_buffer_size=(bytes)
21
+ def self.client_body_buffer_size=(bytes) # :nodoc:
22
22
  @@client_body_buffer_size = bytes
23
23
  end
24
24
 
25
25
  # returns the maximum size of request bodies to buffer in memory,
26
26
  # amounts larger than this are buffered to the filesystem
27
- def self.client_body_buffer_size
27
+ def self.client_body_buffer_size # :nodoc:
28
28
  @@client_body_buffer_size
29
29
  end
30
30
 
31
+ # for Rack::TempfileReaper in rack 1.6+
32
+ def new_tmpio # :nodoc:
33
+ tmpio = Unicorn::TmpIO.new
34
+ (@parser.env['rack.tempfiles'] ||= []) << tmpio
35
+ tmpio
36
+ end
37
+
31
38
  # Initializes a new TeeInput object. You normally do not have to call
32
39
  # this unless you are writing an HTTP server.
33
- def initialize(socket, request)
40
+ def initialize(socket, request) # :nodoc:
34
41
  @len = request.content_length
35
42
  super
36
43
  @tmp = @len && @len <= @@client_body_buffer_size ?
37
- StringIO.new("") : Unicorn::TmpIO.new
44
+ StringIO.new("") : new_tmpio
38
45
  end
39
46
 
40
47
  # :call-seq:
@@ -118,9 +125,7 @@ private
118
125
  end
119
126
 
120
127
  def tee(buffer)
121
- if buffer && buffer.size > 0
122
- @tmp.write(buffer)
123
- end
128
+ @tmp.write(buffer) if buffer
124
129
  buffer
125
130
  end
126
131
  end
data/lib/unicorn/tmpio.rb CHANGED
@@ -11,19 +11,23 @@ class Unicorn::TmpIO < File
11
11
  # immediately, switched to binary mode, and userspace output
12
12
  # buffering is disabled
13
13
  def self.new
14
+ path = nil
15
+
16
+ # workaround File#path being tainted:
17
+ # https://bugs.ruby-lang.org/issues/14485
14
18
  fp = begin
15
- super("#{Dir::tmpdir}/#{rand}", RDWR|CREAT|EXCL, 0600)
19
+ path = "#{Dir::tmpdir}/#{rand}"
20
+ super(path, RDWR|CREAT|EXCL, 0600)
16
21
  rescue Errno::EEXIST
17
22
  retry
18
23
  end
19
- unlink(fp.path)
24
+
25
+ unlink(path)
20
26
  fp.binmode
21
27
  fp.sync = true
22
28
  fp
23
29
  end
24
30
 
25
- # for easier env["rack.input"] compatibility with Rack <= 1.1
26
- def size
27
- stat.size
28
- end unless File.method_defined?(:size)
31
+ # pretend we're Tempfile for Rack::TempfileReaper
32
+ alias close! close
29
33
  end
data/lib/unicorn/util.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- module Unicorn::Util
3
+ require 'fcntl'
4
+ module Unicorn::Util # :nodoc:
4
5
 
5
6
  # :stopdoc:
6
7
  def self.is_log?(fp)
@@ -10,8 +11,8 @@ module Unicorn::Util
10
11
  fp.stat.file? &&
11
12
  fp.sync &&
12
13
  (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
13
- rescue IOError, Errno::EBADF
14
- false
14
+ rescue IOError, Errno::EBADF
15
+ false
15
16
  end
16
17
 
17
18
  def self.chown_logs(uid, gid)
@@ -63,7 +64,7 @@ module Unicorn::Util
63
64
  fp.reopen(fp.path, "a")
64
65
  else
65
66
  # We should not need this workaround, Ruby can be fixed:
66
- # http://bugs.ruby-lang.org/issues/9036
67
+ # https://bugs.ruby-lang.org/issues/9036
67
68
  # MRI will not call call fclose(3) or freopen(3) here
68
69
  # since there's no associated std{in,out,err} FILE * pointer
69
70
  # This should atomically use dup3(2) (or dup2(2)) syscall
@@ -1 +1 @@
1
- Unicorn::Const::UNICORN_VERSION = '4.7.0'
1
+ Unicorn::Const::UNICORN_VERSION = '6.0.0'
@@ -3,26 +3,87 @@ require "raindrops"
3
3
 
4
4
  # This class and its members can be considered a stable interface
5
5
  # and will not change in a backwards-incompatible fashion between
6
- # releases of \Unicorn. Knowledge of this class is generally not
7
- # not needed for most users of \Unicorn.
6
+ # releases of unicorn. Knowledge of this class is generally not
7
+ # not needed for most users of unicorn.
8
8
  #
9
9
  # Some users may want to access it in the before_fork/after_fork hooks.
10
10
  # See the Unicorn::Configurator RDoc for examples.
11
11
  class Unicorn::Worker
12
12
  # :stopdoc:
13
13
  attr_accessor :nr, :switched
14
- attr_writer :tmp
14
+ attr_reader :to_io # IO.select-compatible
15
+ attr_reader :master
15
16
 
16
17
  PER_DROP = Raindrops::PAGE_SIZE / Raindrops::SIZE
17
18
  DROPS = []
18
19
 
19
- def initialize(nr)
20
+ def initialize(nr, pipe=nil)
20
21
  drop_index = nr / PER_DROP
21
22
  @raindrop = DROPS[drop_index] ||= Raindrops.new(PER_DROP)
22
23
  @offset = nr % PER_DROP
23
24
  @raindrop[@offset] = 0
24
25
  @nr = nr
25
- @tmp = @switched = false
26
+ @switched = false
27
+ @to_io, @master = pipe || Unicorn.pipe
28
+ end
29
+
30
+ def atfork_child # :nodoc:
31
+ # we _must_ close in child, parent just holds this open to signal
32
+ @master = @master.close
33
+ end
34
+
35
+ # master fakes SIGQUIT using this
36
+ def quit # :nodoc:
37
+ @master = @master.close if @master
38
+ end
39
+
40
+ # parent does not read
41
+ def atfork_parent # :nodoc:
42
+ @to_io = @to_io.close
43
+ end
44
+
45
+ # call a signal handler immediately without triggering EINTR
46
+ # We do not use the more obvious Process.kill(sig, $$) here since
47
+ # that signal delivery may be deferred. We want to avoid signal delivery
48
+ # while the Rack app.call is running because some database drivers
49
+ # (e.g. ruby-pg) may cancel pending requests.
50
+ def fake_sig(sig) # :nodoc:
51
+ old_cb = trap(sig, "IGNORE")
52
+ old_cb.call
53
+ ensure
54
+ trap(sig, old_cb)
55
+ end
56
+
57
+ # master sends fake signals to children
58
+ def soft_kill(sig) # :nodoc:
59
+ case sig
60
+ when Integer
61
+ signum = sig
62
+ else
63
+ signum = Signal.list[sig.to_s] or
64
+ raise ArgumentError, "BUG: bad signal: #{sig.inspect}"
65
+ end
66
+ # writing and reading 4 bytes on a pipe is atomic on all POSIX platforms
67
+ # Do not care in the odd case the buffer is full, here.
68
+ @master.kgio_trywrite([signum].pack('l'))
69
+ rescue Errno::EPIPE
70
+ # worker will be reaped soon
71
+ end
72
+
73
+ # this only runs when the Rack app.call is not running
74
+ # act like a listener
75
+ def kgio_tryaccept # :nodoc:
76
+ case buf = @to_io.kgio_tryread(4)
77
+ when String
78
+ # unpack the buffer and trigger the signal handler
79
+ signum = buf.unpack('l')
80
+ fake_sig(signum[0])
81
+ # keep looping, more signals may be queued
82
+ when nil # EOF: master died, but we are at a safe place to exit
83
+ fake_sig(:QUIT)
84
+ when :wait_readable # keep waiting
85
+ return false
86
+ end while true # loop, as multiple signals may be sent
26
87
  end
27
88
 
28
89
  # worker objects may be compared to just plain Integers
@@ -40,17 +101,10 @@ class Unicorn::Worker
40
101
  @raindrop[@offset]
41
102
  end
42
103
 
43
- # only exists for compatibility
44
- def tmp # :nodoc:
45
- @tmp ||= begin
46
- tmp = Unicorn::TmpIO.new
47
- tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
48
- tmp
49
- end
50
- end
51
-
104
+ # called in both the master (reaping worker) and worker (SIGQUIT handler)
52
105
  def close # :nodoc:
53
- @tmp.close if @tmp
106
+ @master.close if @master
107
+ @to_io.close if @to_io
54
108
  end
55
109
 
56
110
  # :startdoc:
@@ -58,30 +112,53 @@ class Unicorn::Worker
58
112
  # In most cases, you should be using the Unicorn::Configurator#user
59
113
  # directive instead. This method should only be used if you need
60
114
  # fine-grained control of exactly when you want to change permissions
61
- # in your after_fork hooks.
115
+ # in your after_fork or after_worker_ready hooks, or if you want to
116
+ # use the chroot support.
62
117
  #
63
- # Changes the worker process to the specified +user+ and +group+
118
+ # Changes the worker process to the specified +user+ and +group+,
119
+ # and chroots to the current working directory if +chroot+ is set.
64
120
  # This is only intended to be called from within the worker
65
121
  # process from the +after_fork+ hook. This should be called in
66
122
  # the +after_fork+ hook after any privileged functions need to be
67
123
  # run (e.g. to set per-worker CPU affinity, niceness, etc)
68
124
  #
125
+ # +group+ can be specified as a string, or as an array of two
126
+ # strings. If an array of two strings is given, the first string
127
+ # is used as the primary group of the process, and the second is
128
+ # used as the group of the log files.
129
+ #
69
130
  # Any and all errors raised within this method will be propagated
70
131
  # directly back to the caller (usually the +after_fork+ hook.
71
132
  # These errors commonly include ArgumentError for specifying an
72
- # invalid user/group and Errno::EPERM for insufficient privileges
73
- def user(user, group = nil)
133
+ # invalid user/group and Errno::EPERM for insufficient privileges.
134
+ #
135
+ # chroot support is only available in unicorn 5.3.0+
136
+ # user and group switching appeared in unicorn 0.94.0 (2009-11-05)
137
+ def user(user, group = nil, chroot = false)
74
138
  # we do not protect the caller, checking Process.euid == 0 is
75
139
  # insufficient because modern systems have fine-grained
76
140
  # capabilities. Let the caller handle any and all errors.
77
141
  uid = Etc.getpwnam(user).uid
78
- gid = Etc.getgrnam(group).gid if group
79
- Unicorn::Util.chown_logs(uid, gid)
80
- @tmp.chown(uid, gid) if @tmp
142
+
143
+ if group
144
+ if group.is_a?(Array)
145
+ group, log_group = group
146
+ log_gid = Etc.getgrnam(log_group).gid
147
+ end
148
+ gid = Etc.getgrnam(group).gid
149
+ log_gid ||= gid
150
+ end
151
+
152
+ Unicorn::Util.chown_logs(uid, log_gid)
81
153
  if gid && Process.egid != gid
82
154
  Process.initgroups(user, gid)
83
155
  Process::GID.change_privilege(gid)
84
156
  end
157
+ if chroot
158
+ chroot = Dir.pwd if chroot == true
159
+ Dir.chroot(chroot)
160
+ Dir.chdir('/')
161
+ end
85
162
  Process.euid != uid and Process::UID.change_privilege(uid)
86
163
  @switched = true
87
164
  end