puma 4.3.8-java → 5.0.0.beta1-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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +58 -21
  3. data/LICENSE +23 -20
  4. data/README.md +17 -11
  5. data/docs/deployment.md +3 -1
  6. data/docs/fork_worker.md +31 -0
  7. data/docs/jungle/README.md +13 -0
  8. data/{tools → docs}/jungle/rc.d/README.md +0 -0
  9. data/{tools → docs}/jungle/rc.d/puma +0 -0
  10. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  11. data/{tools → docs}/jungle/upstart/README.md +0 -0
  12. data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
  13. data/{tools → docs}/jungle/upstart/puma.conf +0 -0
  14. data/docs/signals.md +1 -0
  15. data/docs/systemd.md +1 -63
  16. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  17. data/ext/puma_http11/extconf.rb +3 -2
  18. data/ext/puma_http11/http11_parser.c +65 -67
  19. data/ext/puma_http11/http11_parser.rl +1 -3
  20. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  21. data/ext/puma_http11/puma_http11.c +2 -38
  22. data/lib/puma.rb +4 -0
  23. data/lib/puma/app/status.rb +16 -5
  24. data/lib/puma/binder.rb +62 -60
  25. data/lib/puma/cli.rb +7 -15
  26. data/lib/puma/client.rb +35 -32
  27. data/lib/puma/cluster.rb +179 -74
  28. data/lib/puma/configuration.rb +30 -42
  29. data/lib/puma/const.rb +2 -3
  30. data/lib/puma/control_cli.rb +27 -17
  31. data/lib/puma/detect.rb +8 -0
  32. data/lib/puma/dsl.rb +70 -34
  33. data/lib/puma/io_buffer.rb +9 -2
  34. data/lib/puma/jruby_restart.rb +0 -58
  35. data/lib/puma/launcher.rb +41 -29
  36. data/lib/puma/minissl.rb +13 -8
  37. data/lib/puma/null_io.rb +1 -1
  38. data/lib/puma/plugin.rb +1 -10
  39. data/lib/puma/puma_http11.jar +0 -0
  40. data/lib/puma/rack/builder.rb +0 -4
  41. data/lib/puma/reactor.rb +6 -1
  42. data/lib/puma/runner.rb +5 -34
  43. data/lib/puma/server.rb +70 -190
  44. data/lib/puma/single.rb +7 -64
  45. data/lib/puma/state_file.rb +5 -2
  46. data/lib/puma/thread_pool.rb +85 -47
  47. data/lib/rack/handler/puma.rb +1 -3
  48. data/tools/{docker/Dockerfile → Dockerfile} +0 -0
  49. metadata +18 -22
  50. data/docs/tcp_mode.md +0 -96
  51. data/ext/puma_http11/io_buffer.c +0 -155
  52. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  53. data/lib/puma/tcp_logger.rb +0 -41
  54. data/tools/jungle/README.md +0 -19
  55. data/tools/jungle/init.d/README.md +0 -61
  56. data/tools/jungle/init.d/puma +0 -421
  57. data/tools/jungle/init.d/run-puma +0 -18
data/lib/puma/launcher.rb CHANGED
@@ -48,7 +48,8 @@ module Puma
48
48
  @config = conf
49
49
 
50
50
  @binder = Binder.new(@events)
51
- @binder.import_from_env
51
+ @binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
52
+ @binder.create_activated_fds(ENV).each { |k| ENV.delete k }
52
53
 
53
54
  @environment = conf.environment
54
55
 
@@ -69,10 +70,6 @@ module Puma
69
70
  unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
70
71
  end
71
72
 
72
- if @options[:daemon] && Puma.windows?
73
- unsupported 'daemon mode not supported on Windows'
74
- end
75
-
76
73
  Dir.chdir(@restart_dir)
77
74
 
78
75
  prune_bundler if prune_bundler?
@@ -105,6 +102,7 @@ module Puma
105
102
  write_pid
106
103
 
107
104
  path = @options[:state]
105
+ permission = @options[:state_permission]
108
106
  return unless path
109
107
 
110
108
  require 'puma/state_file'
@@ -114,7 +112,7 @@ module Puma
114
112
  sf.control_url = @options[:control_url]
115
113
  sf.control_auth_token = @options[:control_auth_token]
116
114
 
117
- sf.save path
115
+ sf.save path, permission
118
116
  end
119
117
 
120
118
  # Delete the configured pidfile
@@ -184,12 +182,12 @@ module Puma
184
182
  when :exit
185
183
  # nothing
186
184
  end
187
- @binder.close_unix_paths
185
+ close_binder_listeners unless @status == :restart
188
186
  end
189
187
 
190
- # Return which tcp port the launcher is using, if it's using TCP
191
- def connected_port
192
- @binder.connected_port
188
+ # Return all tcp ports the launcher may be using, TCP or SSL
189
+ def connected_ports
190
+ @binder.connected_ports
193
191
  end
194
192
 
195
193
  def restart_args
@@ -202,9 +200,21 @@ module Puma
202
200
  end
203
201
 
204
202
  def close_binder_listeners
203
+ @runner.close_control_listeners
205
204
  @binder.close_listeners
206
205
  end
207
206
 
207
+ def thread_status
208
+ Thread.list.each do |thread|
209
+ name = "Thread: TID-#{thread.object_id.to_s(36)}"
210
+ name += " #{thread['label']}" if thread['label']
211
+ name += " #{thread.name}" if thread.respond_to?(:name) && thread.name
212
+ backtrace = thread.backtrace || ["<no backtrace available>"]
213
+
214
+ yield name, backtrace
215
+ end
216
+ end
217
+
208
218
  private
209
219
 
210
220
  # If configured, write the pid of the current process out
@@ -225,7 +235,7 @@ module Puma
225
235
  end
226
236
 
227
237
  def restart!
228
- @config.run_hooks :on_restart, self
238
+ @config.run_hooks :on_restart, self, @events
229
239
 
230
240
  if Puma.jruby?
231
241
  close_binder_listeners
@@ -241,6 +251,7 @@ module Puma
241
251
  else
242
252
  argv = restart_args
243
253
  Dir.chdir(@restart_dir)
254
+ ENV.update(@binder.redirects_for_restart_env)
244
255
  argv += [@binder.redirects_for_restart]
245
256
  Kernel.exec(*argv)
246
257
  end
@@ -286,8 +297,10 @@ module Puma
286
297
 
287
298
  log '* Pruning Bundler environment'
288
299
  home = ENV['GEM_HOME']
289
- Bundler.with_clean_env do
300
+ bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
301
+ with_unbundled_env do
290
302
  ENV['GEM_HOME'] = home
303
+ ENV['BUNDLE_GEMFILE'] = bundle_gemfile
291
304
  ENV['PUMA_BUNDLER_PRUNED'] = '1'
292
305
  args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
293
306
  # Ruby 2.0+ defaults to true which breaks socket activation
@@ -323,21 +336,6 @@ module Puma
323
336
  log "- Goodbye!"
324
337
  end
325
338
 
326
- def log_thread_status
327
- Thread.list.each do |thread|
328
- log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
329
- logstr = "Thread: TID-#{thread.object_id.to_s(36)}"
330
- logstr += " #{thread.name}" if thread.respond_to?(:name)
331
- log logstr
332
-
333
- if thread.backtrace
334
- log thread.backtrace.join("\n")
335
- else
336
- log "<no backtrace available>"
337
- end
338
- end
339
- end
340
-
341
339
  def set_process_title
342
340
  Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
343
341
  end
@@ -456,8 +454,13 @@ module Puma
456
454
  end
457
455
 
458
456
  begin
459
- Signal.trap "SIGINFO" do
460
- log_thread_status
457
+ unless Puma.jruby? # INFO in use by JVM already
458
+ Signal.trap "SIGINFO" do
459
+ thread_status do |name, backtrace|
460
+ @events.log name
461
+ @events.log backtrace.map { |bt| " #{bt}" }
462
+ end
463
+ end
461
464
  end
462
465
  rescue Exception
463
466
  # Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
@@ -471,5 +474,14 @@ module Puma
471
474
  raise "#{feature} is not supported on your version of RubyGems. " \
472
475
  "You must have RubyGems #{min_version}+ to use this feature."
473
476
  end
477
+
478
+ def with_unbundled_env
479
+ bundler_ver = Gem::Version.new(Bundler::VERSION)
480
+ if bundler_ver < Gem::Version.new('2.1.0')
481
+ Bundler.with_clean_env { yield }
482
+ else
483
+ Bundler.with_unbundled_env { yield }
484
+ end
485
+ end
474
486
  end
475
487
  end
data/lib/puma/minissl.rb CHANGED
@@ -125,11 +125,14 @@ module Puma
125
125
 
126
126
  def read_and_drop(timeout = 1)
127
127
  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
128
+ case @socket.read_nonblock(1024, exception: false)
129
+ when nil
130
+ :eof
131
+ when :wait_readable
132
+ :eagain
133
+ else
134
+ :drop
135
+ end
133
136
  end
134
137
 
135
138
  def should_drop_bytes?
@@ -141,9 +144,7 @@ module Puma
141
144
  # Read any drop any partially initialized sockets and any received bytes during shutdown.
142
145
  # Don't let this socket hold this loop forever.
143
146
  # 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
147
+ return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
147
148
  rescue IOError, SystemCallError
148
149
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
149
150
  # nothing
@@ -270,6 +271,10 @@ module Puma
270
271
  Socket.new io, engine
271
272
  end
272
273
 
274
+ def addr
275
+ @socket.addr
276
+ end
277
+
273
278
  def close
274
279
  @socket.close unless @socket.closed? # closed? call is for Windows
275
280
  end
data/lib/puma/null_io.rb CHANGED
@@ -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
data/lib/puma/plugin.rb CHANGED
@@ -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
  }
data/lib/puma/reactor.rb CHANGED
@@ -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
data/lib/puma/runner.rb CHANGED
@@ -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
@@ -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
data/lib/puma/server.rb CHANGED
@@ -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,20 +86,18 @@ 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
100
- end
101
-
102
101
  # On Linux, use TCP_CORK to better control how the TCP stack
103
102
  # packetizes our stream. This improves both latency and throughput.
104
103
  #
@@ -172,107 +171,6 @@ module Puma
172
171
  @thread_pool and @thread_pool.pool_capacity
173
172
  end
174
173
 
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
174
  # Runs the server.
277
175
  #
278
176
  # If +background+ is true (the default) then a thread is spun
@@ -286,15 +184,9 @@ module Puma
286
184
 
287
185
  @status = :run
288
186
 
289
- if @mode == :tcp
290
- return run_lopez_mode(background)
291
- end
292
-
293
- queue_requests = @queue_requests
294
-
295
187
  @thread_pool = ThreadPool.new(@min_threads,
296
188
  @max_threads,
297
- IOBuffer) do |client, buffer|
189
+ ::Puma::IOBuffer) do |client, buffer|
298
190
 
299
191
  # Advertise this server into the thread
300
192
  Thread.current[ThreadLocalKey] = self
@@ -302,10 +194,10 @@ module Puma
302
194
  process_now = false
303
195
 
304
196
  begin
305
- if queue_requests
197
+ if @queue_requests
306
198
  process_now = client.eagerly_finish
307
199
  else
308
- client.finish
200
+ client.finish(@first_data_timeout)
309
201
  process_now = true
310
202
  end
311
203
  rescue MiniSSL::SSLError => e
@@ -331,11 +223,14 @@ module Puma
331
223
  @reactor.add client
332
224
  end
333
225
  end
226
+
227
+ process_now
334
228
  end
335
229
 
230
+ @thread_pool.out_of_band_hook = @options[:out_of_band]
336
231
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
337
232
 
338
- if queue_requests
233
+ if @queue_requests
339
234
  @reactor = Reactor.new self, @thread_pool
340
235
  @reactor.run_in_thread
341
236
  end
@@ -362,6 +257,7 @@ module Puma
362
257
  end
363
258
 
364
259
  def handle_servers
260
+ @check, @notify = Puma::Util.pipe unless @notify
365
261
  begin
366
262
  check = @check
367
263
  sockets = [check] + @binder.ios
@@ -386,6 +282,10 @@ module Puma
386
282
  break if handle_check
387
283
  else
388
284
  begin
285
+ pool.wait_until_not_full
286
+ pool.wait_for_less_busy_worker(
287
+ @options[:wait_for_less_busy_worker].to_f)
288
+
389
289
  if io = sock.accept_nonblock
390
290
  client = Client.new io, @binder.env(sock)
391
291
  if remote_addr_value
@@ -395,10 +295,6 @@ module Puma
395
295
  end
396
296
 
397
297
  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
298
  end
403
299
  rescue SystemCallError
404
300
  # nothing
@@ -419,17 +315,20 @@ module Puma
419
315
 
420
316
  @events.fire :state, @status
421
317
 
422
- graceful_shutdown if @status == :stop || @status == :restart
423
318
  if queue_requests
319
+ @queue_requests = false
424
320
  @reactor.clear!
425
321
  @reactor.shutdown
426
322
  end
323
+ graceful_shutdown if @status == :stop || @status == :restart
427
324
  rescue Exception => e
428
325
  STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
429
326
  STDERR.puts e.backtrace
430
327
  ensure
431
- @check.close
328
+ @check.close unless @check.closed? # Ruby 2.2 issue
432
329
  @notify.close
330
+ @notify = nil
331
+ @check = nil
433
332
  end
434
333
 
435
334
  @events.fire :state, :done
@@ -476,29 +375,24 @@ module Puma
476
375
  close_socket = false
477
376
  return
478
377
  when true
479
- return unless @queue_requests
480
378
  buffer.reset
481
379
 
482
380
  ThreadPool.clean_thread_locals if clean_thread_locals
483
381
 
484
382
  requests += 1
485
383
 
486
- # Closing keepalive sockets after they've made a reasonable
487
- # number of requests allows Puma to service many connections
488
- # fairly, even when the number of concurrent connections exceeds
489
- # the size of the threadpool. It also allows cluster mode Pumas
490
- # to keep load evenly distributed across workers, because clients
491
- # are randomly assigned a new worker when opening a new connection.
492
- #
493
- # Previously, Puma would kick connections in this conditional back
494
- # to the reactor. However, because this causes the todo set to increase
495
- # in size, the wait_until_full mutex would never unlock, leaving
496
- # any additional connections unserviced.
497
- break if requests >= MAX_FAST_INLINE
498
-
499
384
  check_for_more_data = @status == :run
500
385
 
386
+ if requests >= MAX_FAST_INLINE
387
+ # This will mean that reset will only try to use the data it already
388
+ # has buffered and won't try to read more data. What this means is that
389
+ # every client, independent of their request speed, gets treated like a slow
390
+ # one once every MAX_FAST_INLINE requests.
391
+ check_for_more_data = false
392
+ end
393
+
501
394
  unless client.reset(check_for_more_data)
395
+ return unless @queue_requests
502
396
  close_socket = false
503
397
  client.set_timeout @persistent_timeout
504
398
  @reactor.add client
@@ -631,6 +525,8 @@ module Puma
631
525
  #
632
526
  # Finally, it'll return +true+ on keep-alive connections.
633
527
  def handle_request(req, lines)
528
+ @requests_count +=1
529
+
634
530
  env = req.env
635
531
  client = req.io
636
532
 
@@ -677,37 +573,6 @@ module Puma
677
573
  }
678
574
  end
679
575
 
680
- # Fixup any headers with , in the name to have _ now. We emit
681
- # headers with , in them during the parse phase to avoid ambiguity
682
- # with the - to _ conversion for critical headers. But here for
683
- # compatibility, we'll convert them back. This code is written to
684
- # avoid allocation in the common case (ie there are no headers
685
- # with , in their names), that's why it has the extra conditionals.
686
-
687
- to_delete = nil
688
- to_add = nil
689
-
690
- env.each do |k,v|
691
- if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
692
- if to_delete
693
- to_delete << k
694
- else
695
- to_delete = [k]
696
- end
697
-
698
- unless to_add
699
- to_add = {}
700
- end
701
-
702
- to_add[k.tr(",", "_")] = v
703
- end
704
- end
705
-
706
- if to_delete
707
- to_delete.each { |k| env.delete(k) }
708
- env.merge! to_add
709
- end
710
-
711
576
  # A rack extension. If the app writes #call'ables to this
712
577
  # array, we will invoke them when the request is done.
713
578
  #
@@ -729,17 +594,14 @@ module Puma
729
594
  return :async
730
595
  end
731
596
  rescue ThreadPool::ForceShutdown => e
732
- @events.log "Detected force shutdown of a thread, returning 503"
733
- @events.unknown_error self, e, "Rack app"
734
-
735
- status = 503
736
- headers = {}
737
- res_body = ["Request was internally terminated early\n"]
597
+ @events.unknown_error self, e, "Rack app", env
598
+ @events.log "Detected force shutdown of a thread"
738
599
 
600
+ status, headers, res_body = lowlevel_error(e, env, 503)
739
601
  rescue Exception => e
740
602
  @events.unknown_error self, e, "Rack app", env
741
603
 
742
- status, headers, res_body = lowlevel_error(e, env)
604
+ status, headers, res_body = lowlevel_error(e, env, 500)
743
605
  end
744
606
 
745
607
  content_length = nil
@@ -754,10 +616,10 @@ module Puma
754
616
  line_ending = LINE_END
755
617
  colon = COLON
756
618
 
757
- http_11 = if env[HTTP_VERSION] == HTTP_11
619
+ http_11 = env[HTTP_VERSION] == HTTP_11
620
+ if http_11
758
621
  allow_chunked = true
759
622
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
760
- include_keepalive_header = false
761
623
 
762
624
  # An optimization. The most common response is 200, so we can
763
625
  # reply with the proper 200 status without having to compute
@@ -771,11 +633,9 @@ module Puma
771
633
 
772
634
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
773
635
  end
774
- true
775
636
  else
776
637
  allow_chunked = false
777
638
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
778
- include_keepalive_header = keep_alive
779
639
 
780
640
  # Same optimization as above for HTTP/1.1
781
641
  #
@@ -787,9 +647,12 @@ module Puma
787
647
 
788
648
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
789
649
  end
790
- false
791
650
  end
792
651
 
652
+ # regardless of what the client wants, we always close the connection
653
+ # if running without request queueing
654
+ keep_alive &&= @queue_requests
655
+
793
656
  response_hijack = nil
794
657
 
795
658
  headers.each do |k, vs|
@@ -816,10 +679,15 @@ module Puma
816
679
  end
817
680
  end
818
681
 
819
- if include_keepalive_header
820
- lines << CONNECTION_KEEP_ALIVE
821
- elsif http_11 && !keep_alive
822
- lines << CONNECTION_CLOSE
682
+ # HTTP/1.1 & 1.0 assume different defaults:
683
+ # - HTTP 1.0 assumes the connection will be closed if not specified
684
+ # - HTTP 1.1 assumes the connection will be kept alive if not specified.
685
+ # Only set the header if we're doing something which is not the default
686
+ # for this protocol version
687
+ if http_11
688
+ lines << CONNECTION_CLOSE if !keep_alive
689
+ else
690
+ lines << CONNECTION_KEEP_ALIVE if keep_alive
823
691
  end
824
692
 
825
693
  if no_body
@@ -946,19 +814,21 @@ module Puma
946
814
 
947
815
  # A fallback rack response if +@app+ raises as exception.
948
816
  #
949
- def lowlevel_error(e, env)
817
+ def lowlevel_error(e, env, status=500)
950
818
  if handler = @options[:lowlevel_error_handler]
951
819
  if handler.arity == 1
952
820
  return handler.call(e)
953
- else
821
+ elsif handler.arity == 2
954
822
  return handler.call(e, env)
823
+ else
824
+ return handler.call(e, env, status)
955
825
  end
956
826
  end
957
827
 
958
828
  if @leak_stack_on_error
959
- [500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
829
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
960
830
  else
961
- [500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
831
+ [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
962
832
  end
963
833
  end
964
834
 
@@ -1016,6 +886,7 @@ module Puma
1016
886
  end
1017
887
 
1018
888
  def notify_safely(message)
889
+ @check, @notify = Puma::Util.pipe unless @notify
1019
890
  begin
1020
891
  @notify << message
1021
892
  rescue IOError
@@ -1045,8 +916,9 @@ module Puma
1045
916
  @thread.join if @thread && sync
1046
917
  end
1047
918
 
1048
- def begin_restart
919
+ def begin_restart(sync=false)
1049
920
  notify_safely(RESTART_COMMAND)
921
+ @thread.join if @thread && sync
1050
922
  end
1051
923
 
1052
924
  def fast_write(io, str)
@@ -1084,5 +956,13 @@ module Puma
1084
956
  HTTP_INJECTION_REGEX =~ header_value.to_s
1085
957
  end
1086
958
  private :possible_header_injection?
959
+
960
+ # List of methods invoked by #stats.
961
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
962
+
963
+ # Returns a hash of stats about the running server for reporting purposes.
964
+ def stats
965
+ STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
966
+ end
1087
967
  end
1088
968
  end