puma 4.3.3-java → 5.0.0.beta2-java

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +79 -8
  3. data/LICENSE +23 -20
  4. data/README.md +18 -12
  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 +5 -4
  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 +12 -2
  22. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  23. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +37 -6
  24. data/ext/puma_http11/puma_http11.c +3 -38
  25. data/lib/puma.rb +5 -0
  26. data/lib/puma/app/status.rb +18 -3
  27. data/lib/puma/binder.rb +66 -63
  28. data/lib/puma/cli.rb +7 -15
  29. data/lib/puma/client.rb +64 -14
  30. data/lib/puma/cluster.rb +183 -74
  31. data/lib/puma/commonlogger.rb +2 -2
  32. data/lib/puma/configuration.rb +30 -42
  33. data/lib/puma/const.rb +2 -3
  34. data/lib/puma/control_cli.rb +27 -17
  35. data/lib/puma/detect.rb +8 -0
  36. data/lib/puma/dsl.rb +72 -36
  37. data/lib/puma/error_logger.rb +96 -0
  38. data/lib/puma/events.rb +33 -31
  39. data/lib/puma/io_buffer.rb +9 -2
  40. data/lib/puma/jruby_restart.rb +0 -58
  41. data/lib/puma/launcher.rb +46 -31
  42. data/lib/puma/minissl.rb +47 -10
  43. data/lib/puma/null_io.rb +1 -1
  44. data/lib/puma/plugin.rb +1 -10
  45. data/lib/puma/puma_http11.jar +0 -0
  46. data/lib/puma/rack/builder.rb +0 -4
  47. data/lib/puma/reactor.rb +8 -3
  48. data/lib/puma/runner.rb +6 -35
  49. data/lib/puma/server.rb +138 -182
  50. data/lib/puma/single.rb +7 -64
  51. data/lib/puma/state_file.rb +6 -3
  52. data/lib/puma/thread_pool.rb +90 -49
  53. data/lib/rack/handler/puma.rb +1 -3
  54. data/tools/{docker/Dockerfile → Dockerfile} +0 -0
  55. metadata +18 -21
  56. data/docs/tcp_mode.md +0 -96
  57. data/ext/puma_http11/io_buffer.c +0 -155
  58. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  59. data/lib/puma/tcp_logger.rb +0 -41
  60. data/tools/jungle/README.md +0 -19
  61. data/tools/jungle/init.d/README.md +0 -61
  62. data/tools/jungle/init.d/puma +0 -421
  63. 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
+
14
+ # define constant at runtime, as it's easy to determine at built time,
15
+ # but Puma could (it shouldn't) be loaded with an older OpenSSL version
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,24 @@ 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
+ #
42
+ def ssl_version_state
43
+ IS_JRUBY ? [nil, nil] : @engine.ssl_vers_st
44
+ end
45
+
46
+ # used to check the handshake status, in particular when a TCP connection
47
+ # is made with TLSv1.3 as an available protocol
48
+ def bad_tlsv1_3?
49
+ HAS_TLS1_3 && @engine.ssl_vers_st == ['TLSv1.3', 'SSLERR']
50
+ end
51
+ private :bad_tlsv1_3?
52
+
25
53
  def readpartial(size)
26
54
  while true
27
55
  output = @engine.read
@@ -41,6 +69,7 @@ module Puma
41
69
 
42
70
  def engine_read_all
43
71
  output = @engine.read
72
+ raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
44
73
  while output and additional_output = @engine.read
45
74
  output << additional_output
46
75
  end
@@ -125,11 +154,14 @@ module Puma
125
154
 
126
155
  def read_and_drop(timeout = 1)
127
156
  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
157
+ case @socket.read_nonblock(1024, exception: false)
158
+ when nil
159
+ :eof
160
+ when :wait_readable
161
+ :eagain
162
+ else
163
+ :drop
164
+ end
133
165
  end
134
166
 
135
167
  def should_drop_bytes?
@@ -141,9 +173,7 @@ module Puma
141
173
  # Read any drop any partially initialized sockets and any received bytes during shutdown.
142
174
  # Don't let this socket hold this loop forever.
143
175
  # 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
176
+ return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
147
177
  rescue IOError, SystemCallError
148
178
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
149
179
  # nothing
@@ -166,7 +196,10 @@ module Puma
166
196
  end
167
197
  end
168
198
 
169
- if defined?(JRUBY_VERSION)
199
+ if IS_JRUBY
200
+ OPENSSL_NO_SSL3 = false
201
+ OPENSSL_NO_TLS1 = false
202
+
170
203
  class SSLError < StandardError
171
204
  # Define this for jruby even though it isn't used.
172
205
  end
@@ -183,7 +216,7 @@ module Puma
183
216
  @no_tlsv1_1 = false
184
217
  end
185
218
 
186
- if defined?(JRUBY_VERSION)
219
+ if IS_JRUBY
187
220
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
188
221
  attr_reader :keystore
189
222
  attr_accessor :keystore_pass
@@ -270,6 +303,10 @@ module Puma
270
303
  Socket.new io, engine
271
304
  end
272
305
 
306
+ def addr
307
+ @socket.addr
308
+ end
309
+
273
310
  def close
274
311
  @socket.close unless @socket.closed? # closed? call is for Windows
275
312
  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
Binary file
@@ -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
  }
@@ -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
 
@@ -18,10 +18,6 @@ module Puma
18
18
  @started_at = Time.now
19
19
  end
20
20
 
21
- def daemon?
22
- @options[:daemon]
23
- end
24
-
25
21
  def development?
26
22
  @options[:environment] == "development"
27
23
  end
@@ -34,7 +30,7 @@ module Puma
34
30
  @events.log str
35
31
  end
36
32
 
37
- def before_restart
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,16 @@ 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
+ def close_control_listeners
68
+ @control.binder.close_listeners if @control
69
+ end
70
+
91
71
  def ruby_engine
92
72
  if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
93
73
  "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
@@ -108,10 +88,6 @@ module Puma
108
88
  log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
109
89
  log "* Min threads: #{min_t}, max threads: #{max_t}"
110
90
  log "* Environment: #{ENV['RACK_ENV']}"
111
-
112
- if @options[:mode] == :tcp
113
- log "* Mode: Lopez Express (tcp)"
114
- end
115
91
  end
116
92
 
117
93
  def redirected_io?
@@ -150,7 +126,6 @@ module Puma
150
126
  exit 1
151
127
  end
152
128
 
153
- # Load the app before we daemonize.
154
129
  begin
155
130
  @app = @launcher.config.app
156
131
  rescue Exception => e
@@ -174,10 +149,6 @@ module Puma
174
149
  server.max_threads = max_t
175
150
  server.inherit_binder @launcher.binder
176
151
 
177
- if @options[:mode] == :tcp
178
- server.tcp_mode!
179
- end
180
-
181
152
  if @options[:early_hints]
182
153
  server.early_hints = true
183
154
  end
@@ -11,6 +11,7 @@ require 'puma/client'
11
11
  require 'puma/binder'
12
12
  require 'puma/accept_nonblock'
13
13
  require 'puma/util'
14
+ require 'puma/io_buffer'
14
15
 
15
16
  require 'puma/puma_http11'
16
17
 
@@ -36,6 +37,7 @@ module Puma
36
37
 
37
38
  attr_reader :thread
38
39
  attr_reader :events
40
+ attr_reader :requests_count
39
41
  attr_accessor :app
40
42
 
41
43
  attr_accessor :min_threads
@@ -57,8 +59,7 @@ module Puma
57
59
  @app = app
58
60
  @events = events
59
61
 
60
- @check, @notify = Puma::Util.pipe
61
-
62
+ @check, @notify = nil
62
63
  @status = :stop
63
64
 
64
65
  @min_threads = 0
@@ -85,24 +86,34 @@ module Puma
85
86
  @mode = :http
86
87
 
87
88
  @precheck_closing = true
89
+
90
+ @requests_count = 0
88
91
  end
89
92
 
90
93
  attr_accessor :binder, :leak_stack_on_error, :early_hints
91
94
 
92
- def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_port
95
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
93
96
 
94
97
  def inherit_binder(bind)
95
98
  @binder = bind
96
99
  end
97
100
 
98
- def tcp_mode!
99
- @mode = :tcp
101
+ class << self
102
+ # :nodoc:
103
+ def tcp_cork_supported?
104
+ RbConfig::CONFIG['host_os'] =~ /linux/ &&
105
+ Socket.const_defined?(:IPPROTO_TCP) &&
106
+ Socket.const_defined?(:TCP_CORK) &&
107
+ Socket.const_defined?(:SOL_TCP) &&
108
+ Socket.const_defined?(:TCP_INFO)
109
+ end
110
+ private :tcp_cork_supported?
100
111
  end
101
112
 
102
113
  # On Linux, use TCP_CORK to better control how the TCP stack
103
114
  # packetizes our stream. This improves both latency and throughput.
104
115
  #
105
- if RUBY_PLATFORM =~ /linux/
116
+ if tcp_cork_supported?
106
117
  UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
107
118
 
108
119
  # 6 == Socket::IPPROTO_TCP
@@ -110,7 +121,7 @@ module Puma
110
121
  # 1/0 == turn on/off
111
122
  def cork_socket(socket)
112
123
  begin
113
- socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
124
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if socket.kind_of? TCPSocket
114
125
  rescue IOError, SystemCallError
115
126
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
116
127
  end
@@ -118,7 +129,7 @@ module Puma
118
129
 
119
130
  def uncork_socket(socket)
120
131
  begin
121
- socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
132
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if socket.kind_of? TCPSocket
122
133
  rescue IOError, SystemCallError
123
134
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
124
135
  end
@@ -172,107 +183,6 @@ module Puma
172
183
  @thread_pool and @thread_pool.pool_capacity
173
184
  end
174
185
 
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
186
  # Runs the server.
277
187
  #
278
188
  # If +background+ is true (the default) then a thread is spun
@@ -286,15 +196,9 @@ module Puma
286
196
 
287
197
  @status = :run
288
198
 
289
- if @mode == :tcp
290
- return run_lopez_mode(background)
291
- end
292
-
293
- queue_requests = @queue_requests
294
-
295
199
  @thread_pool = ThreadPool.new(@min_threads,
296
200
  @max_threads,
297
- IOBuffer) do |client, buffer|
201
+ ::Puma::IOBuffer) do |client, buffer|
298
202
 
299
203
  # Advertise this server into the thread
300
204
  Thread.current[ThreadLocalKey] = self
@@ -302,10 +206,10 @@ module Puma
302
206
  process_now = false
303
207
 
304
208
  begin
305
- if queue_requests
209
+ if @queue_requests
306
210
  process_now = client.eagerly_finish
307
211
  else
308
- client.finish
212
+ client.finish(@first_data_timeout)
309
213
  process_now = true
310
214
  end
311
215
  rescue MiniSSL::SSLError => e
@@ -315,14 +219,16 @@ module Puma
315
219
 
316
220
  client.close
317
221
 
318
- @events.ssl_error self, addr, cert, e
222
+ @events.ssl_error e, addr, cert
319
223
  rescue HttpParserError => e
320
224
  client.write_error(400)
321
225
  client.close
322
226
 
323
- @events.parse_error self, client.env, e
324
- rescue ConnectionError, EOFError
227
+ @events.parse_error e, client
228
+ rescue ConnectionError, EOFError => e
325
229
  client.close
230
+
231
+ @events.connection_error e, client
326
232
  else
327
233
  if process_now
328
234
  process_client client, buffer
@@ -331,11 +237,14 @@ module Puma
331
237
  @reactor.add client
332
238
  end
333
239
  end
240
+
241
+ process_now
334
242
  end
335
243
 
244
+ @thread_pool.out_of_band_hook = @options[:out_of_band]
336
245
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
337
246
 
338
- if queue_requests
247
+ if @queue_requests
339
248
  @reactor = Reactor.new self, @thread_pool
340
249
  @reactor.run_in_thread
341
250
  end
@@ -362,6 +271,7 @@ module Puma
362
271
  end
363
272
 
364
273
  def handle_servers
274
+ @check, @notify = Puma::Util.pipe unless @notify
365
275
  begin
366
276
  check = @check
367
277
  sockets = [check] + @binder.ios
@@ -385,51 +295,49 @@ module Puma
385
295
  if sock == check
386
296
  break if handle_check
387
297
  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
298
+ pool.wait_until_not_full
299
+ pool.wait_for_less_busy_worker(
300
+ @options[:wait_for_less_busy_worker].to_f)
301
+
302
+ io = begin
303
+ sock.accept_nonblock
304
+ rescue IO::WaitReadable
305
+ next
412
306
  end
307
+ client = Client.new io, @binder.env(sock)
308
+ if remote_addr_value
309
+ client.peerip = remote_addr_value
310
+ elsif remote_addr_header
311
+ client.remote_addr_header = remote_addr_header
312
+ end
313
+ pool << client
413
314
  end
414
315
  end
415
316
  rescue Object => e
416
- @events.unknown_error self, e, "Listen loop"
317
+ @events.unknown_error e, nil, "Listen loop"
417
318
  end
418
319
  end
419
320
 
420
321
  @events.fire :state, @status
421
322
 
422
- graceful_shutdown if @status == :stop || @status == :restart
423
323
  if queue_requests
324
+ @queue_requests = false
424
325
  @reactor.clear!
425
326
  @reactor.shutdown
426
327
  end
328
+ graceful_shutdown if @status == :stop || @status == :restart
427
329
  rescue Exception => e
428
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
429
- STDERR.puts e.backtrace
330
+ @events.unknown_error e, nil, "Exception handling servers"
430
331
  ensure
431
- @check.close
332
+ begin
333
+ @check.close unless @check.closed?
334
+ rescue Errno::EBADF, RuntimeError
335
+ # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
336
+ # Errno::EBADF is infrequently raised
337
+ end
432
338
  @notify.close
339
+ @notify = nil
340
+ @check = nil
433
341
  end
434
342
 
435
343
  @events.fire :state, :done
@@ -476,7 +384,6 @@ module Puma
476
384
  close_socket = false
477
385
  return
478
386
  when true
479
- return unless @queue_requests
480
387
  buffer.reset
481
388
 
482
389
  ThreadPool.clean_thread_locals if clean_thread_locals
@@ -494,6 +401,7 @@ module Puma
494
401
  end
495
402
 
496
403
  unless client.reset(check_for_more_data)
404
+ return unless @queue_requests
497
405
  close_socket = false
498
406
  client.set_timeout @persistent_timeout
499
407
  @reactor.add client
@@ -516,7 +424,7 @@ module Puma
516
424
 
517
425
  close_socket = true
518
426
 
519
- @events.ssl_error self, addr, cert, e
427
+ @events.ssl_error e, addr, cert
520
428
 
521
429
  # The client doesn't know HTTP well
522
430
  rescue HttpParserError => e
@@ -524,7 +432,7 @@ module Puma
524
432
 
525
433
  client.write_error(400)
526
434
 
527
- @events.parse_error self, client.env, e
435
+ @events.parse_error e, client
528
436
 
529
437
  # Server error
530
438
  rescue StandardError => e
@@ -532,8 +440,7 @@ module Puma
532
440
 
533
441
  client.write_error(500)
534
442
 
535
- @events.unknown_error self, e, "Read"
536
-
443
+ @events.unknown_error e, nil, "Read"
537
444
  ensure
538
445
  buffer.reset
539
446
 
@@ -543,7 +450,7 @@ module Puma
543
450
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
544
451
  # Already closed
545
452
  rescue StandardError => e
546
- @events.unknown_error self, e, "Client"
453
+ @events.unknown_error e, nil, "Client"
547
454
  end
548
455
  end
549
456
  end
@@ -579,7 +486,7 @@ module Puma
579
486
 
580
487
  env[PATH_INFO] = env[REQUEST_PATH]
581
488
 
582
- # From http://www.ietf.org/rfc/rfc3875 :
489
+ # From https://www.ietf.org/rfc/rfc3875 :
583
490
  # "Script authors should be aware that the REMOTE_ADDR and
584
491
  # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
585
492
  # may not identify the ultimate source of the request.
@@ -626,6 +533,8 @@ module Puma
626
533
  #
627
534
  # Finally, it'll return +true+ on keep-alive connections.
628
535
  def handle_request(req, lines)
536
+ @requests_count +=1
537
+
629
538
  env = req.env
630
539
  client = req.io
631
540
 
@@ -666,12 +575,44 @@ module Puma
666
575
  end
667
576
 
668
577
  fast_write client, "\r\n".freeze
669
- rescue ConnectionError
578
+ rescue ConnectionError => e
579
+ @events.debug_error e
670
580
  # noop, if we lost the socket we just won't send the early hints
671
581
  end
672
582
  }
673
583
  end
674
584
 
585
+ # Fixup any headers with , in the name to have _ now. We emit
586
+ # headers with , in them during the parse phase to avoid ambiguity
587
+ # with the - to _ conversion for critical headers. But here for
588
+ # compatibility, we'll convert them back. This code is written to
589
+ # avoid allocation in the common case (ie there are no headers
590
+ # with , in their names), that's why it has the extra conditionals.
591
+
592
+ to_delete = nil
593
+ to_add = nil
594
+
595
+ env.each do |k,v|
596
+ if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
597
+ if to_delete
598
+ to_delete << k
599
+ else
600
+ to_delete = [k]
601
+ end
602
+
603
+ unless to_add
604
+ to_add = {}
605
+ end
606
+
607
+ to_add[k.tr(",", "_")] = v
608
+ end
609
+ end
610
+
611
+ if to_delete
612
+ to_delete.each { |k| env.delete(k) }
613
+ env.merge! to_add
614
+ end
615
+
675
616
  # A rack extension. If the app writes #call'ables to this
676
617
  # array, we will invoke them when the request is done.
677
618
  #
@@ -693,17 +634,14 @@ module Puma
693
634
  return :async
694
635
  end
695
636
  rescue ThreadPool::ForceShutdown => e
696
- @events.log "Detected force shutdown of a thread, returning 503"
697
- @events.unknown_error self, e, "Rack app"
698
-
699
- status = 503
700
- headers = {}
701
- res_body = ["Request was internally terminated early\n"]
637
+ @events.unknown_error e, req, "Rack app"
638
+ @events.log "Detected force shutdown of a thread"
702
639
 
640
+ status, headers, res_body = lowlevel_error(e, env, 503)
703
641
  rescue Exception => e
704
- @events.unknown_error self, e, "Rack app", env
642
+ @events.unknown_error e, req, "Rack app"
705
643
 
706
- status, headers, res_body = lowlevel_error(e, env)
644
+ status, headers, res_body = lowlevel_error(e, env, 500)
707
645
  end
708
646
 
709
647
  content_length = nil
@@ -718,10 +656,10 @@ module Puma
718
656
  line_ending = LINE_END
719
657
  colon = COLON
720
658
 
721
- http_11 = if env[HTTP_VERSION] == HTTP_11
659
+ http_11 = env[HTTP_VERSION] == HTTP_11
660
+ if http_11
722
661
  allow_chunked = true
723
662
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
724
- include_keepalive_header = false
725
663
 
726
664
  # An optimization. The most common response is 200, so we can
727
665
  # reply with the proper 200 status without having to compute
@@ -735,11 +673,9 @@ module Puma
735
673
 
736
674
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
737
675
  end
738
- true
739
676
  else
740
677
  allow_chunked = false
741
678
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
742
- include_keepalive_header = keep_alive
743
679
 
744
680
  # Same optimization as above for HTTP/1.1
745
681
  #
@@ -751,9 +687,12 @@ module Puma
751
687
 
752
688
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
753
689
  end
754
- false
755
690
  end
756
691
 
692
+ # regardless of what the client wants, we always close the connection
693
+ # if running without request queueing
694
+ keep_alive &&= @queue_requests
695
+
757
696
  response_hijack = nil
758
697
 
759
698
  headers.each do |k, vs|
@@ -780,10 +719,15 @@ module Puma
780
719
  end
781
720
  end
782
721
 
783
- if include_keepalive_header
784
- lines << CONNECTION_KEEP_ALIVE
785
- elsif http_11 && !keep_alive
786
- lines << CONNECTION_CLOSE
722
+ # HTTP/1.1 & 1.0 assume different defaults:
723
+ # - HTTP 1.0 assumes the connection will be closed if not specified
724
+ # - HTTP 1.1 assumes the connection will be kept alive if not specified.
725
+ # Only set the header if we're doing something which is not the default
726
+ # for this protocol version
727
+ if http_11
728
+ lines << CONNECTION_CLOSE if !keep_alive
729
+ else
730
+ lines << CONNECTION_KEEP_ALIVE if keep_alive
787
731
  end
788
732
 
789
733
  if no_body
@@ -910,19 +854,21 @@ module Puma
910
854
 
911
855
  # A fallback rack response if +@app+ raises as exception.
912
856
  #
913
- def lowlevel_error(e, env)
857
+ def lowlevel_error(e, env, status=500)
914
858
  if handler = @options[:lowlevel_error_handler]
915
859
  if handler.arity == 1
916
860
  return handler.call(e)
917
- else
861
+ elsif handler.arity == 2
918
862
  return handler.call(e, env)
863
+ else
864
+ return handler.call(e, env, status)
919
865
  end
920
866
  end
921
867
 
922
868
  if @leak_stack_on_error
923
- [500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
869
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
924
870
  else
925
- [500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
871
+ [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
926
872
  end
927
873
  end
928
874
 
@@ -980,9 +926,10 @@ module Puma
980
926
  end
981
927
 
982
928
  def notify_safely(message)
929
+ @check, @notify = Puma::Util.pipe unless @notify
983
930
  begin
984
931
  @notify << message
985
- rescue IOError
932
+ rescue IOError, NoMethodError, Errno::EPIPE
986
933
  # The server, in another thread, is shutting down
987
934
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
988
935
  rescue RuntimeError => e
@@ -1009,8 +956,9 @@ module Puma
1009
956
  @thread.join if @thread && sync
1010
957
  end
1011
958
 
1012
- def begin_restart
959
+ def begin_restart(sync=false)
1013
960
  notify_safely(RESTART_COMMAND)
961
+ @thread.join if @thread && sync
1014
962
  end
1015
963
 
1016
964
  def fast_write(io, str)
@@ -1048,5 +996,13 @@ module Puma
1048
996
  HTTP_INJECTION_REGEX =~ header_value.to_s
1049
997
  end
1050
998
  private :possible_header_injection?
999
+
1000
+ # List of methods invoked by #stats.
1001
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
1002
+
1003
+ # Returns a hash of stats about the running server for reporting purposes.
1004
+ def stats
1005
+ STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
1006
+ end
1051
1007
  end
1052
1008
  end