puma 4.3.1 → 5.0.0

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.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +94 -3
  3. data/LICENSE +23 -20
  4. data/README.md +26 -13
  5. data/docs/architecture.md +3 -3
  6. data/docs/deployment.md +9 -3
  7. data/docs/fork_worker.md +31 -0
  8. data/docs/jungle/README.md +13 -0
  9. data/{tools → docs}/jungle/rc.d/README.md +0 -0
  10. data/{tools → docs}/jungle/rc.d/puma +0 -0
  11. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  12. data/{tools → docs}/jungle/upstart/README.md +0 -0
  13. data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
  14. data/{tools → docs}/jungle/upstart/puma.conf +0 -0
  15. data/docs/signals.md +7 -6
  16. data/docs/systemd.md +1 -63
  17. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  18. data/ext/puma_http11/extconf.rb +4 -3
  19. data/ext/puma_http11/http11_parser.c +3 -1
  20. data/ext/puma_http11/http11_parser.rl +3 -1
  21. data/ext/puma_http11/mini_ssl.c +15 -2
  22. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  23. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  24. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
  25. data/ext/puma_http11/puma_http11.c +7 -38
  26. data/lib/puma.rb +17 -0
  27. data/lib/puma/app/status.rb +18 -3
  28. data/lib/puma/binder.rb +88 -68
  29. data/lib/puma/cli.rb +7 -15
  30. data/lib/puma/client.rb +67 -14
  31. data/lib/puma/cluster.rb +191 -74
  32. data/lib/puma/commonlogger.rb +2 -2
  33. data/lib/puma/configuration.rb +31 -42
  34. data/lib/puma/const.rb +4 -3
  35. data/lib/puma/control_cli.rb +29 -17
  36. data/lib/puma/detect.rb +17 -0
  37. data/lib/puma/dsl.rb +144 -70
  38. data/lib/puma/error_logger.rb +97 -0
  39. data/lib/puma/events.rb +35 -31
  40. data/lib/puma/io_buffer.rb +9 -2
  41. data/lib/puma/jruby_restart.rb +0 -58
  42. data/lib/puma/launcher.rb +49 -31
  43. data/lib/puma/minissl.rb +60 -18
  44. data/lib/puma/minissl/context_builder.rb +0 -3
  45. data/lib/puma/null_io.rb +1 -1
  46. data/lib/puma/plugin.rb +1 -10
  47. data/lib/puma/rack/builder.rb +0 -4
  48. data/lib/puma/reactor.rb +9 -4
  49. data/lib/puma/runner.rb +8 -36
  50. data/lib/puma/server.rb +149 -186
  51. data/lib/puma/single.rb +7 -64
  52. data/lib/puma/state_file.rb +6 -3
  53. data/lib/puma/thread_pool.rb +94 -49
  54. data/lib/rack/handler/puma.rb +1 -3
  55. data/tools/{docker/Dockerfile → Dockerfile} +0 -0
  56. metadata +21 -23
  57. data/docs/tcp_mode.md +0 -96
  58. data/ext/puma_http11/io_buffer.c +0 -155
  59. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  60. data/lib/puma/tcp_logger.rb +0 -41
  61. data/tools/jungle/README.md +0 -19
  62. data/tools/jungle/init.d/README.md +0 -61
  63. data/tools/jungle/init.d/puma +0 -421
  64. data/tools/jungle/init.d/run-puma +0 -18
@@ -5,8 +5,18 @@ begin
5
5
  rescue LoadError
6
6
  end
7
7
 
8
+ # need for Puma::MiniSSL::OPENSSL constants used in `HAS_TLS1_3`
9
+ require 'puma/puma_http11'
10
+
8
11
  module Puma
9
12
  module MiniSSL
13
+ # Define constant at runtime, as it's easy to determine at built time,
14
+ # but Puma could (it shouldn't) be loaded with an older OpenSSL version
15
+ # @version 5.0.0
16
+ HAS_TLS1_3 = !IS_JRUBY &&
17
+ (OPENSSL_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) != -1 &&
18
+ (OPENSSL_LIBRARY_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) !=-1
19
+
10
20
  class Socket
11
21
  def initialize(socket, engine)
12
22
  @socket = socket
@@ -22,6 +32,26 @@ module Puma
22
32
  @socket.closed?
23
33
  end
24
34
 
35
+ # Returns a two element array,
36
+ # first is protocol version (SSL_get_version),
37
+ # second is 'handshake' state (SSL_state_string)
38
+ #
39
+ # Used for dropping tcp connections to ssl.
40
+ # See OpenSSL ssl/ssl_stat.c SSL_state_string for info
41
+ # @version 5.0.0
42
+ #
43
+ def ssl_version_state
44
+ IS_JRUBY ? [nil, nil] : @engine.ssl_vers_st
45
+ end
46
+
47
+ # Used to check the handshake status, in particular when a TCP connection
48
+ # is made with TLSv1.3 as an available protocol
49
+ # @version 5.0.0
50
+ def bad_tlsv1_3?
51
+ HAS_TLS1_3 && @engine.ssl_vers_st == ['TLSv1.3', 'SSLERR']
52
+ end
53
+ private :bad_tlsv1_3?
54
+
25
55
  def readpartial(size)
26
56
  while true
27
57
  output = @engine.read
@@ -41,6 +71,7 @@ module Puma
41
71
 
42
72
  def engine_read_all
43
73
  output = @engine.read
74
+ raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
44
75
  while output and additional_output = @engine.read
45
76
  output << additional_output
46
77
  end
@@ -107,14 +138,18 @@ module Puma
107
138
  alias_method :<<, :write
108
139
 
109
140
  # This is a temporary fix to deal with websockets code using
110
- # write_nonblock. The problem with implementing it properly
141
+ # write_nonblock.
142
+
143
+ # The problem with implementing it properly
111
144
  # is that it means we'd have to have the ability to rewind
112
145
  # an engine because after we write+extract, the socket
113
146
  # write_nonblock call might raise an exception and later
114
147
  # code would pass the same data in, but the engine would think
115
- # it had already written the data in. So for the time being
116
- # (and since write blocking is quite rare), go ahead and actually
117
- # block in write_nonblock.
148
+ # it had already written the data in.
149
+ #
150
+ # So for the time being (and since write blocking is quite rare),
151
+ # go ahead and actually block in write_nonblock.
152
+ #
118
153
  def write_nonblock(data, *_)
119
154
  write data
120
155
  end
@@ -125,11 +160,14 @@ module Puma
125
160
 
126
161
  def read_and_drop(timeout = 1)
127
162
  return :timeout unless IO.select([@socket], nil, nil, timeout)
128
- return :eof unless read_nonblock(1024)
129
- :drop
130
- rescue Errno::EAGAIN
131
- # do nothing
132
- :eagain
163
+ case @socket.read_nonblock(1024, exception: false)
164
+ when nil
165
+ :eof
166
+ when :wait_readable
167
+ :eagain
168
+ else
169
+ :drop
170
+ end
133
171
  end
134
172
 
135
173
  def should_drop_bytes?
@@ -141,9 +179,7 @@ module Puma
141
179
  # Read any drop any partially initialized sockets and any received bytes during shutdown.
142
180
  # Don't let this socket hold this loop forever.
143
181
  # If it can't send more packets within 1s, then give up.
144
- while should_drop_bytes?
145
- return if [:timeout, :eof].include?(read_and_drop(1))
146
- end
182
+ return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
147
183
  rescue IOError, SystemCallError
148
184
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
149
185
  # nothing
@@ -166,12 +202,13 @@ module Puma
166
202
  end
167
203
  end
168
204
 
169
- if defined?(JRUBY_VERSION)
205
+ if IS_JRUBY
206
+ OPENSSL_NO_SSL3 = false
207
+ OPENSSL_NO_TLS1 = false
208
+
170
209
  class SSLError < StandardError
171
210
  # Define this for jruby even though it isn't used.
172
211
  end
173
-
174
- def self.check; end
175
212
  end
176
213
 
177
214
  class Context
@@ -183,7 +220,7 @@ module Puma
183
220
  @no_tlsv1_1 = false
184
221
  end
185
222
 
186
- if defined?(JRUBY_VERSION)
223
+ if IS_JRUBY
187
224
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
188
225
  attr_reader :keystore
189
226
  attr_accessor :keystore_pass
@@ -228,13 +265,13 @@ module Puma
228
265
 
229
266
  # disables TLSv1
230
267
  def no_tlsv1=(tlsv1)
231
- raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
268
+ raise ArgumentError, "Invalid value of no_tlsv1=" unless ['true', 'false', true, false].include?(tlsv1)
232
269
  @no_tlsv1 = tlsv1
233
270
  end
234
271
 
235
272
  # disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
236
273
  def no_tlsv1_1=(tlsv1_1)
237
- raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
274
+ raise ArgumentError, "Invalid value of no_tlsv1_1=" unless ['true', 'false', true, false].include?(tlsv1_1)
238
275
  @no_tlsv1_1 = tlsv1_1
239
276
  end
240
277
 
@@ -270,6 +307,11 @@ module Puma
270
307
  Socket.new io, engine
271
308
  end
272
309
 
310
+ # @version 5.0.0
311
+ def addr
312
+ @socket.addr
313
+ end
314
+
273
315
  def close
274
316
  @socket.close unless @socket.closed? # closed? call is for Windows
275
317
  end
@@ -2,9 +2,6 @@ module Puma
2
2
  module MiniSSL
3
3
  class ContextBuilder
4
4
  def initialize(params, events)
5
- require 'puma/minissl'
6
- MiniSSL.check
7
-
8
5
  @params = params
9
6
  @events = events
10
7
  end
@@ -15,7 +15,7 @@ module Puma
15
15
  # Mimics IO#read with no data.
16
16
  #
17
17
  def read(count = nil, _buffer = nil)
18
- (count && count > 0) ? nil : ""
18
+ count && count > 0 ? nil : ""
19
19
  end
20
20
 
21
21
  def rewind
@@ -10,7 +10,7 @@ module Puma
10
10
 
11
11
  def create(name)
12
12
  if cls = Plugins.find(name)
13
- plugin = cls.new(Plugin)
13
+ plugin = cls.new
14
14
  @instances << plugin
15
15
  return plugin
16
16
  end
@@ -104,17 +104,8 @@ module Puma
104
104
  Plugins.register name, cls
105
105
  end
106
106
 
107
- def initialize(loader)
108
- @loader = loader
109
- end
110
-
111
107
  def in_background(&blk)
112
108
  Plugins.add_background blk
113
109
  end
114
-
115
- def workers_supported?
116
- return false if Puma.jruby? || Puma.windows?
117
- true
118
- end
119
110
  end
120
111
  end
@@ -67,10 +67,6 @@ module Puma::Rack
67
67
  options[:environment] = e
68
68
  }
69
69
 
70
- opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
71
- options[:daemonize] = d ? true : false
72
- }
73
-
74
70
  opts.on("-P", "--pid FILE", "file to store PID") { |f|
75
71
  options[:pid] = ::File.expand_path(f)
76
72
  }
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'puma/util'
4
- require 'puma/minissl'
4
+ require 'puma/minissl' if ::Puma::HAS_SSL
5
5
 
6
6
  require 'nio'
7
7
 
@@ -189,7 +189,12 @@ module Puma
189
189
  if submon.value == @ready
190
190
  false
191
191
  else
192
- submon.value.close
192
+ if submon.value.can_close?
193
+ submon.value.close
194
+ else
195
+ # Pass remaining open client connections to the thread pool.
196
+ @app_pool << submon.value
197
+ end
193
198
  begin
194
199
  selector.deregister submon.value
195
200
  rescue IOError
@@ -247,7 +252,7 @@ module Puma
247
252
  c.close
248
253
  clear_monitor mon
249
254
 
250
- @events.ssl_error @server, addr, cert, e
255
+ @events.ssl_error e, addr, cert
251
256
 
252
257
  # The client doesn't know HTTP well
253
258
  rescue HttpParserError => e
@@ -258,7 +263,7 @@ module Puma
258
263
 
259
264
  clear_monitor mon
260
265
 
261
- @events.parse_error @server, c.env, e
266
+ @events.parse_error e, c
262
267
  rescue StandardError => e
263
268
  @server.lowlevel_error(e, c.env)
264
269
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'puma/server'
4
4
  require 'puma/const'
5
- require 'puma/minissl/context_builder'
6
5
 
7
6
  module Puma
8
7
  # Generic class that is used by `Puma::Cluster` and `Puma::Single` to
@@ -18,10 +17,6 @@ module Puma
18
17
  @started_at = Time.now
19
18
  end
20
19
 
21
- def daemon?
22
- @options[:daemon]
23
- end
24
-
25
20
  def development?
26
21
  @options[:environment] == "development"
27
22
  end
@@ -34,7 +29,8 @@ module Puma
34
29
  @events.log str
35
30
  end
36
31
 
37
- def before_restart
32
+ # @version 5.0.0
33
+ def stop_control
38
34
  @control.stop(true) if @control
39
35
  end
40
36
 
@@ -52,8 +48,6 @@ module Puma
52
48
 
53
49
  require 'puma/app/status'
54
50
 
55
- uri = URI.parse str
56
-
57
51
  if token = @options[:control_auth_token]
58
52
  token = nil if token.empty? || token == 'none'
59
53
  end
@@ -64,30 +58,17 @@ module Puma
64
58
  control.min_threads = 0
65
59
  control.max_threads = 1
66
60
 
67
- case uri.scheme
68
- when "ssl"
69
- log "* Starting control server on #{str}"
70
- params = Util.parse_query uri.query
71
- ctx = MiniSSL::ContextBuilder.new(params, @events).context
72
-
73
- control.add_ssl_listener uri.host, uri.port, ctx
74
- when "tcp"
75
- log "* Starting control server on #{str}"
76
- control.add_tcp_listener uri.host, uri.port
77
- when "unix"
78
- log "* Starting control server on #{str}"
79
- path = "#{uri.host}#{uri.path}"
80
- mask = @options[:control_url_umask]
81
-
82
- control.add_unix_listener path, mask
83
- else
84
- error "Invalid control URI: #{str}"
85
- end
61
+ control.binder.parse [str], self, 'Starting control server'
86
62
 
87
63
  control.run
88
64
  @control = control
89
65
  end
90
66
 
67
+ # @version 5.0.0
68
+ def close_control_listeners
69
+ @control.binder.close_listeners if @control
70
+ end
71
+
91
72
  def ruby_engine
92
73
  if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
93
74
  "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
@@ -108,10 +89,6 @@ module Puma
108
89
  log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
109
90
  log "* Min threads: #{min_t}, max threads: #{max_t}"
110
91
  log "* Environment: #{ENV['RACK_ENV']}"
111
-
112
- if @options[:mode] == :tcp
113
- log "* Mode: Lopez Express (tcp)"
114
- end
115
92
  end
116
93
 
117
94
  def redirected_io?
@@ -150,7 +127,6 @@ module Puma
150
127
  exit 1
151
128
  end
152
129
 
153
- # Load the app before we daemonize.
154
130
  begin
155
131
  @app = @launcher.config.app
156
132
  rescue Exception => e
@@ -174,10 +150,6 @@ module Puma
174
150
  server.max_threads = max_t
175
151
  server.inherit_binder @launcher.binder
176
152
 
177
- if @options[:mode] == :tcp
178
- server.tcp_mode!
179
- end
180
-
181
153
  if @options[:early_hints]
182
154
  server.early_hints = true
183
155
  end
@@ -9,10 +9,8 @@ require 'puma/null_io'
9
9
  require 'puma/reactor'
10
10
  require 'puma/client'
11
11
  require 'puma/binder'
12
- require 'puma/accept_nonblock'
13
12
  require 'puma/util'
14
-
15
- require 'puma/puma_http11'
13
+ require 'puma/io_buffer'
16
14
 
17
15
  require 'socket'
18
16
  require 'forwardable'
@@ -36,6 +34,7 @@ module Puma
36
34
 
37
35
  attr_reader :thread
38
36
  attr_reader :events
37
+ attr_reader :requests_count # @version 5.0.0
39
38
  attr_accessor :app
40
39
 
41
40
  attr_accessor :min_threads
@@ -57,8 +56,7 @@ module Puma
57
56
  @app = app
58
57
  @events = events
59
58
 
60
- @check, @notify = Puma::Util.pipe
61
-
59
+ @check, @notify = nil
62
60
  @status = :stop
63
61
 
64
62
  @min_threads = 0
@@ -85,24 +83,34 @@ module Puma
85
83
  @mode = :http
86
84
 
87
85
  @precheck_closing = true
86
+
87
+ @requests_count = 0
88
88
  end
89
89
 
90
90
  attr_accessor :binder, :leak_stack_on_error, :early_hints
91
91
 
92
- def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_port
92
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
93
93
 
94
94
  def inherit_binder(bind)
95
95
  @binder = bind
96
96
  end
97
97
 
98
- def tcp_mode!
99
- @mode = :tcp
98
+ class << self
99
+ # :nodoc:
100
+ # @version 5.0.0
101
+ def tcp_cork_supported?
102
+ RbConfig::CONFIG['host_os'] =~ /linux/ &&
103
+ Socket.const_defined?(:IPPROTO_TCP) &&
104
+ Socket.const_defined?(:TCP_CORK) &&
105
+ Socket.const_defined?(:TCP_INFO)
106
+ end
107
+ private :tcp_cork_supported?
100
108
  end
101
109
 
102
110
  # On Linux, use TCP_CORK to better control how the TCP stack
103
111
  # packetizes our stream. This improves both latency and throughput.
104
112
  #
105
- if RUBY_PLATFORM =~ /linux/
113
+ if tcp_cork_supported?
106
114
  UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
107
115
 
108
116
  # 6 == Socket::IPPROTO_TCP
@@ -110,7 +118,7 @@ module Puma
110
118
  # 1/0 == turn on/off
111
119
  def cork_socket(socket)
112
120
  begin
113
- socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
121
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if socket.kind_of? TCPSocket
114
122
  rescue IOError, SystemCallError
115
123
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
116
124
  end
@@ -118,7 +126,7 @@ module Puma
118
126
 
119
127
  def uncork_socket(socket)
120
128
  begin
121
- socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
129
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if socket.kind_of? TCPSocket
122
130
  rescue IOError, SystemCallError
123
131
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
124
132
  end
@@ -129,7 +137,7 @@ module Puma
129
137
  return false unless @precheck_closing
130
138
 
131
139
  begin
132
- tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
140
+ tcp_info = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
133
141
  rescue IOError, SystemCallError
134
142
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
135
143
  @precheck_closing = false
@@ -172,107 +180,6 @@ module Puma
172
180
  @thread_pool and @thread_pool.pool_capacity
173
181
  end
174
182
 
175
- # Lopez Mode == raw tcp apps
176
-
177
- def run_lopez_mode(background=true)
178
- @thread_pool = ThreadPool.new(@min_threads,
179
- @max_threads,
180
- Hash) do |client, tl|
181
-
182
- io = client.to_io
183
- addr = io.peeraddr.last
184
-
185
- if addr.empty?
186
- # Set unix socket addrs to localhost
187
- addr = "127.0.0.1:0"
188
- else
189
- addr = "#{addr}:#{io.peeraddr[1]}"
190
- end
191
-
192
- env = { 'thread' => tl, REMOTE_ADDR => addr }
193
-
194
- begin
195
- @app.call env, client.to_io
196
- rescue Object => e
197
- STDERR.puts "! Detected exception at toplevel: #{e.message} (#{e.class})"
198
- STDERR.puts e.backtrace
199
- end
200
-
201
- client.close unless env['detach']
202
- end
203
-
204
- @events.fire :state, :running
205
-
206
- if background
207
- @thread = Thread.new do
208
- Puma.set_thread_name "server"
209
- handle_servers_lopez_mode
210
- end
211
- return @thread
212
- else
213
- handle_servers_lopez_mode
214
- end
215
- end
216
-
217
- def handle_servers_lopez_mode
218
- begin
219
- check = @check
220
- sockets = [check] + @binder.ios
221
- pool = @thread_pool
222
-
223
- while @status == :run
224
- begin
225
- ios = IO.select sockets
226
- ios.first.each do |sock|
227
- if sock == check
228
- break if handle_check
229
- else
230
- begin
231
- if io = sock.accept_nonblock
232
- client = Client.new io, nil
233
- pool << client
234
- end
235
- rescue SystemCallError
236
- # nothing
237
- rescue Errno::ECONNABORTED
238
- # client closed the socket even before accept
239
- begin
240
- io.close
241
- rescue
242
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
243
- end
244
- end
245
- end
246
- end
247
- rescue Object => e
248
- @events.unknown_error self, e, "Listen loop"
249
- end
250
- end
251
-
252
- @events.fire :state, @status
253
-
254
- graceful_shutdown if @status == :stop || @status == :restart
255
-
256
- rescue Exception => e
257
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
258
- STDERR.puts e.backtrace
259
- ensure
260
- begin
261
- @check.close
262
- rescue
263
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
264
- end
265
-
266
- # Prevent can't modify frozen IOError (RuntimeError)
267
- begin
268
- @notify.close
269
- rescue IOError
270
- # no biggy
271
- end
272
- end
273
-
274
- @events.fire :state, :done
275
- end
276
183
  # Runs the server.
277
184
  #
278
185
  # If +background+ is true (the default) then a thread is spun
@@ -286,15 +193,9 @@ module Puma
286
193
 
287
194
  @status = :run
288
195
 
289
- if @mode == :tcp
290
- return run_lopez_mode(background)
291
- end
292
-
293
- queue_requests = @queue_requests
294
-
295
196
  @thread_pool = ThreadPool.new(@min_threads,
296
197
  @max_threads,
297
- IOBuffer) do |client, buffer|
198
+ ::Puma::IOBuffer) do |client, buffer|
298
199
 
299
200
  # Advertise this server into the thread
300
201
  Thread.current[ThreadLocalKey] = self
@@ -302,10 +203,10 @@ module Puma
302
203
  process_now = false
303
204
 
304
205
  begin
305
- if queue_requests
206
+ if @queue_requests
306
207
  process_now = client.eagerly_finish
307
208
  else
308
- client.finish
209
+ client.finish(@first_data_timeout)
309
210
  process_now = true
310
211
  end
311
212
  rescue MiniSSL::SSLError => e
@@ -315,14 +216,16 @@ module Puma
315
216
 
316
217
  client.close
317
218
 
318
- @events.ssl_error self, addr, cert, e
219
+ @events.ssl_error e, addr, cert
319
220
  rescue HttpParserError => e
320
221
  client.write_error(400)
321
222
  client.close
322
223
 
323
- @events.parse_error self, client.env, e
324
- rescue ConnectionError, EOFError
224
+ @events.parse_error e, client
225
+ rescue ConnectionError, EOFError => e
325
226
  client.close
227
+
228
+ @events.connection_error e, client
326
229
  else
327
230
  if process_now
328
231
  process_client client, buffer
@@ -331,11 +234,14 @@ module Puma
331
234
  @reactor.add client
332
235
  end
333
236
  end
237
+
238
+ process_now
334
239
  end
335
240
 
241
+ @thread_pool.out_of_band_hook = @options[:out_of_band]
336
242
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
337
243
 
338
- if queue_requests
244
+ if @queue_requests
339
245
  @reactor = Reactor.new self, @thread_pool
340
246
  @reactor.run_in_thread
341
247
  end
@@ -362,6 +268,7 @@ module Puma
362
268
  end
363
269
 
364
270
  def handle_servers
271
+ @check, @notify = Puma::Util.pipe unless @notify
365
272
  begin
366
273
  check = @check
367
274
  sockets = [check] + @binder.ios
@@ -385,51 +292,49 @@ module Puma
385
292
  if sock == check
386
293
  break if handle_check
387
294
  else
388
- begin
389
- if io = sock.accept_nonblock
390
- client = Client.new io, @binder.env(sock)
391
- if remote_addr_value
392
- client.peerip = remote_addr_value
393
- elsif remote_addr_header
394
- client.remote_addr_header = remote_addr_header
395
- end
396
-
397
- pool << client
398
- busy_threads = pool.wait_until_not_full
399
- if busy_threads == 0
400
- @options[:out_of_band].each(&:call) if @options[:out_of_band]
401
- end
402
- end
403
- rescue SystemCallError
404
- # nothing
405
- rescue Errno::ECONNABORTED
406
- # client closed the socket even before accept
407
- begin
408
- io.close
409
- rescue
410
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
411
- end
295
+ pool.wait_until_not_full
296
+ pool.wait_for_less_busy_worker(
297
+ @options[:wait_for_less_busy_worker].to_f)
298
+
299
+ io = begin
300
+ sock.accept_nonblock
301
+ rescue IO::WaitReadable
302
+ next
303
+ end
304
+ client = Client.new io, @binder.env(sock)
305
+ if remote_addr_value
306
+ client.peerip = remote_addr_value
307
+ elsif remote_addr_header
308
+ client.remote_addr_header = remote_addr_header
412
309
  end
310
+ pool << client
413
311
  end
414
312
  end
415
313
  rescue Object => e
416
- @events.unknown_error self, e, "Listen loop"
314
+ @events.unknown_error e, nil, "Listen loop"
417
315
  end
418
316
  end
419
317
 
420
318
  @events.fire :state, @status
421
319
 
422
- graceful_shutdown if @status == :stop || @status == :restart
423
320
  if queue_requests
321
+ @queue_requests = false
424
322
  @reactor.clear!
425
323
  @reactor.shutdown
426
324
  end
325
+ graceful_shutdown if @status == :stop || @status == :restart
427
326
  rescue Exception => e
428
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
429
- STDERR.puts e.backtrace
327
+ @events.unknown_error e, nil, "Exception handling servers"
430
328
  ensure
431
- @check.close
329
+ begin
330
+ @check.close unless @check.closed?
331
+ rescue Errno::EBADF, RuntimeError
332
+ # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
333
+ # Errno::EBADF is infrequently raised
334
+ end
432
335
  @notify.close
336
+ @notify = nil
337
+ @check = nil
433
338
  end
434
339
 
435
340
  @events.fire :state, :done
@@ -476,7 +381,6 @@ module Puma
476
381
  close_socket = false
477
382
  return
478
383
  when true
479
- return unless @queue_requests
480
384
  buffer.reset
481
385
 
482
386
  ThreadPool.clean_thread_locals if clean_thread_locals
@@ -494,6 +398,7 @@ module Puma
494
398
  end
495
399
 
496
400
  unless client.reset(check_for_more_data)
401
+ return unless @queue_requests
497
402
  close_socket = false
498
403
  client.set_timeout @persistent_timeout
499
404
  @reactor.add client
@@ -516,7 +421,7 @@ module Puma
516
421
 
517
422
  close_socket = true
518
423
 
519
- @events.ssl_error self, addr, cert, e
424
+ @events.ssl_error e, addr, cert
520
425
 
521
426
  # The client doesn't know HTTP well
522
427
  rescue HttpParserError => e
@@ -524,7 +429,7 @@ module Puma
524
429
 
525
430
  client.write_error(400)
526
431
 
527
- @events.parse_error self, client.env, e
432
+ @events.parse_error e, client
528
433
 
529
434
  # Server error
530
435
  rescue StandardError => e
@@ -532,8 +437,7 @@ module Puma
532
437
 
533
438
  client.write_error(500)
534
439
 
535
- @events.unknown_error self, e, "Read"
536
-
440
+ @events.unknown_error e, nil, "Read"
537
441
  ensure
538
442
  buffer.reset
539
443
 
@@ -543,7 +447,7 @@ module Puma
543
447
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
544
448
  # Already closed
545
449
  rescue StandardError => e
546
- @events.unknown_error self, e, "Client"
450
+ @events.unknown_error e, nil, "Client"
547
451
  end
548
452
  end
549
453
  end
@@ -579,7 +483,7 @@ module Puma
579
483
 
580
484
  env[PATH_INFO] = env[REQUEST_PATH]
581
485
 
582
- # From http://www.ietf.org/rfc/rfc3875 :
486
+ # From https://www.ietf.org/rfc/rfc3875 :
583
487
  # "Script authors should be aware that the REMOTE_ADDR and
584
488
  # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
585
489
  # may not identify the ultimate source of the request.
@@ -626,6 +530,8 @@ module Puma
626
530
  #
627
531
  # Finally, it'll return +true+ on keep-alive connections.
628
532
  def handle_request(req, lines)
533
+ @requests_count +=1
534
+
629
535
  env = req.env
630
536
  client = req.io
631
537
 
@@ -657,6 +563,7 @@ module Puma
657
563
  headers.each_pair do |k, vs|
658
564
  if vs.respond_to?(:to_s) && !vs.to_s.empty?
659
565
  vs.to_s.split(NEWLINE).each do |v|
566
+ next if possible_header_injection?(v)
660
567
  fast_write client, "#{k}: #{v}\r\n"
661
568
  end
662
569
  else
@@ -665,12 +572,44 @@ module Puma
665
572
  end
666
573
 
667
574
  fast_write client, "\r\n".freeze
668
- rescue ConnectionError
575
+ rescue ConnectionError => e
576
+ @events.debug_error e
669
577
  # noop, if we lost the socket we just won't send the early hints
670
578
  end
671
579
  }
672
580
  end
673
581
 
582
+ # Fixup any headers with , in the name to have _ now. We emit
583
+ # headers with , in them during the parse phase to avoid ambiguity
584
+ # with the - to _ conversion for critical headers. But here for
585
+ # compatibility, we'll convert them back. This code is written to
586
+ # avoid allocation in the common case (ie there are no headers
587
+ # with , in their names), that's why it has the extra conditionals.
588
+
589
+ to_delete = nil
590
+ to_add = nil
591
+
592
+ env.each do |k,v|
593
+ if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
594
+ if to_delete
595
+ to_delete << k
596
+ else
597
+ to_delete = [k]
598
+ end
599
+
600
+ unless to_add
601
+ to_add = {}
602
+ end
603
+
604
+ to_add[k.tr(",", "_")] = v
605
+ end
606
+ end
607
+
608
+ if to_delete
609
+ to_delete.each { |k| env.delete(k) }
610
+ env.merge! to_add
611
+ end
612
+
674
613
  # A rack extension. If the app writes #call'ables to this
675
614
  # array, we will invoke them when the request is done.
676
615
  #
@@ -692,17 +631,14 @@ module Puma
692
631
  return :async
693
632
  end
694
633
  rescue ThreadPool::ForceShutdown => e
695
- @events.log "Detected force shutdown of a thread, returning 503"
696
- @events.unknown_error self, e, "Rack app"
697
-
698
- status = 503
699
- headers = {}
700
- res_body = ["Request was internally terminated early\n"]
634
+ @events.unknown_error e, req, "Rack app"
635
+ @events.log "Detected force shutdown of a thread"
701
636
 
637
+ status, headers, res_body = lowlevel_error(e, env, 503)
702
638
  rescue Exception => e
703
- @events.unknown_error self, e, "Rack app", env
639
+ @events.unknown_error e, req, "Rack app"
704
640
 
705
- status, headers, res_body = lowlevel_error(e, env)
641
+ status, headers, res_body = lowlevel_error(e, env, 500)
706
642
  end
707
643
 
708
644
  content_length = nil
@@ -717,10 +653,10 @@ module Puma
717
653
  line_ending = LINE_END
718
654
  colon = COLON
719
655
 
720
- http_11 = if env[HTTP_VERSION] == HTTP_11
656
+ http_11 = env[HTTP_VERSION] == HTTP_11
657
+ if http_11
721
658
  allow_chunked = true
722
659
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
723
- include_keepalive_header = false
724
660
 
725
661
  # An optimization. The most common response is 200, so we can
726
662
  # reply with the proper 200 status without having to compute
@@ -734,11 +670,9 @@ module Puma
734
670
 
735
671
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
736
672
  end
737
- true
738
673
  else
739
674
  allow_chunked = false
740
675
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
741
- include_keepalive_header = keep_alive
742
676
 
743
677
  # Same optimization as above for HTTP/1.1
744
678
  #
@@ -750,14 +684,18 @@ module Puma
750
684
 
751
685
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
752
686
  end
753
- false
754
687
  end
755
688
 
689
+ # regardless of what the client wants, we always close the connection
690
+ # if running without request queueing
691
+ keep_alive &&= @queue_requests
692
+
756
693
  response_hijack = nil
757
694
 
758
695
  headers.each do |k, vs|
759
696
  case k.downcase
760
697
  when CONTENT_LENGTH2
698
+ next if possible_header_injection?(vs)
761
699
  content_length = vs
762
700
  next
763
701
  when TRANSFER_ENCODING
@@ -770,6 +708,7 @@ module Puma
770
708
 
771
709
  if vs.respond_to?(:to_s) && !vs.to_s.empty?
772
710
  vs.to_s.split(NEWLINE).each do |v|
711
+ next if possible_header_injection?(v)
773
712
  lines.append k, colon, v, line_ending
774
713
  end
775
714
  else
@@ -777,10 +716,15 @@ module Puma
777
716
  end
778
717
  end
779
718
 
780
- if include_keepalive_header
781
- lines << CONNECTION_KEEP_ALIVE
782
- elsif http_11 && !keep_alive
783
- lines << CONNECTION_CLOSE
719
+ # HTTP/1.1 & 1.0 assume different defaults:
720
+ # - HTTP 1.0 assumes the connection will be closed if not specified
721
+ # - HTTP 1.1 assumes the connection will be kept alive if not specified.
722
+ # Only set the header if we're doing something which is not the default
723
+ # for this protocol version
724
+ if http_11
725
+ lines << CONNECTION_CLOSE if !keep_alive
726
+ else
727
+ lines << CONNECTION_KEEP_ALIVE if keep_alive
784
728
  end
785
729
 
786
730
  if no_body
@@ -907,19 +851,21 @@ module Puma
907
851
 
908
852
  # A fallback rack response if +@app+ raises as exception.
909
853
  #
910
- def lowlevel_error(e, env)
854
+ def lowlevel_error(e, env, status=500)
911
855
  if handler = @options[:lowlevel_error_handler]
912
856
  if handler.arity == 1
913
857
  return handler.call(e)
914
- else
858
+ elsif handler.arity == 2
915
859
  return handler.call(e, env)
860
+ else
861
+ return handler.call(e, env, status)
916
862
  end
917
863
  end
918
864
 
919
865
  if @leak_stack_on_error
920
- [500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
866
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
921
867
  else
922
- [500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
868
+ [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
923
869
  end
924
870
  end
925
871
 
@@ -977,9 +923,10 @@ module Puma
977
923
  end
978
924
 
979
925
  def notify_safely(message)
926
+ @check, @notify = Puma::Util.pipe unless @notify
980
927
  begin
981
928
  @notify << message
982
- rescue IOError
929
+ rescue IOError, NoMethodError, Errno::EPIPE
983
930
  # The server, in another thread, is shutting down
984
931
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
985
932
  rescue RuntimeError => e
@@ -1006,8 +953,9 @@ module Puma
1006
953
  @thread.join if @thread && sync
1007
954
  end
1008
955
 
1009
- def begin_restart
956
+ def begin_restart(sync=false)
1010
957
  notify_safely(RESTART_COMMAND)
958
+ @thread.join if @thread && sync
1011
959
  end
1012
960
 
1013
961
  def fast_write(io, str)
@@ -1040,5 +988,20 @@ module Puma
1040
988
  def shutting_down?
1041
989
  @status == :stop || @status == :restart
1042
990
  end
991
+
992
+ def possible_header_injection?(header_value)
993
+ HTTP_INJECTION_REGEX =~ header_value.to_s
994
+ end
995
+ private :possible_header_injection?
996
+
997
+ # List of methods invoked by #stats.
998
+ # @version 5.0.0
999
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
1000
+
1001
+ # Returns a hash of stats about the running server for reporting purposes.
1002
+ # @version 5.0.0
1003
+ def stats
1004
+ STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
1005
+ end
1043
1006
  end
1044
1007
  end