puma 4.3.7-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 +59 -17
  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 +1 -3
  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 +62 -177
  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 +16 -20
  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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,7 +375,6 @@ 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
@@ -494,6 +392,7 @@ module Puma
494
392
  end
495
393
 
496
394
  unless client.reset(check_for_more_data)
395
+ return unless @queue_requests
497
396
  close_socket = false
498
397
  client.set_timeout @persistent_timeout
499
398
  @reactor.add client
@@ -626,6 +525,8 @@ module Puma
626
525
  #
627
526
  # Finally, it'll return +true+ on keep-alive connections.
628
527
  def handle_request(req, lines)
528
+ @requests_count +=1
529
+
629
530
  env = req.env
630
531
  client = req.io
631
532
 
@@ -672,37 +573,6 @@ module Puma
672
573
  }
673
574
  end
674
575
 
675
- # Fixup any headers with , in the name to have _ now. We emit
676
- # headers with , in them during the parse phase to avoid ambiguity
677
- # with the - to _ conversion for critical headers. But here for
678
- # compatibility, we'll convert them back. This code is written to
679
- # avoid allocation in the common case (ie there are no headers
680
- # with , in their names), that's why it has the extra conditionals.
681
-
682
- to_delete = nil
683
- to_add = nil
684
-
685
- env.each do |k,v|
686
- if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
687
- if to_delete
688
- to_delete << k
689
- else
690
- to_delete = [k]
691
- end
692
-
693
- unless to_add
694
- to_add = {}
695
- end
696
-
697
- to_add[k.tr(",", "_")] = v
698
- end
699
- end
700
-
701
- if to_delete
702
- to_delete.each { |k| env.delete(k) }
703
- env.merge! to_add
704
- end
705
-
706
576
  # A rack extension. If the app writes #call'ables to this
707
577
  # array, we will invoke them when the request is done.
708
578
  #
@@ -724,17 +594,14 @@ module Puma
724
594
  return :async
725
595
  end
726
596
  rescue ThreadPool::ForceShutdown => e
727
- @events.log "Detected force shutdown of a thread, returning 503"
728
- @events.unknown_error self, e, "Rack app"
729
-
730
- status = 503
731
- headers = {}
732
- 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"
733
599
 
600
+ status, headers, res_body = lowlevel_error(e, env, 503)
734
601
  rescue Exception => e
735
602
  @events.unknown_error self, e, "Rack app", env
736
603
 
737
- status, headers, res_body = lowlevel_error(e, env)
604
+ status, headers, res_body = lowlevel_error(e, env, 500)
738
605
  end
739
606
 
740
607
  content_length = nil
@@ -749,10 +616,10 @@ module Puma
749
616
  line_ending = LINE_END
750
617
  colon = COLON
751
618
 
752
- http_11 = if env[HTTP_VERSION] == HTTP_11
619
+ http_11 = env[HTTP_VERSION] == HTTP_11
620
+ if http_11
753
621
  allow_chunked = true
754
622
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
755
- include_keepalive_header = false
756
623
 
757
624
  # An optimization. The most common response is 200, so we can
758
625
  # reply with the proper 200 status without having to compute
@@ -766,11 +633,9 @@ module Puma
766
633
 
767
634
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
768
635
  end
769
- true
770
636
  else
771
637
  allow_chunked = false
772
638
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
773
- include_keepalive_header = keep_alive
774
639
 
775
640
  # Same optimization as above for HTTP/1.1
776
641
  #
@@ -782,9 +647,12 @@ module Puma
782
647
 
783
648
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
784
649
  end
785
- false
786
650
  end
787
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
+
788
656
  response_hijack = nil
789
657
 
790
658
  headers.each do |k, vs|
@@ -811,10 +679,15 @@ module Puma
811
679
  end
812
680
  end
813
681
 
814
- if include_keepalive_header
815
- lines << CONNECTION_KEEP_ALIVE
816
- elsif http_11 && !keep_alive
817
- 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
818
691
  end
819
692
 
820
693
  if no_body
@@ -941,19 +814,21 @@ module Puma
941
814
 
942
815
  # A fallback rack response if +@app+ raises as exception.
943
816
  #
944
- def lowlevel_error(e, env)
817
+ def lowlevel_error(e, env, status=500)
945
818
  if handler = @options[:lowlevel_error_handler]
946
819
  if handler.arity == 1
947
820
  return handler.call(e)
948
- else
821
+ elsif handler.arity == 2
949
822
  return handler.call(e, env)
823
+ else
824
+ return handler.call(e, env, status)
950
825
  end
951
826
  end
952
827
 
953
828
  if @leak_stack_on_error
954
- [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")}"]]
955
830
  else
956
- [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"]]
957
832
  end
958
833
  end
959
834
 
@@ -1011,6 +886,7 @@ module Puma
1011
886
  end
1012
887
 
1013
888
  def notify_safely(message)
889
+ @check, @notify = Puma::Util.pipe unless @notify
1014
890
  begin
1015
891
  @notify << message
1016
892
  rescue IOError
@@ -1040,8 +916,9 @@ module Puma
1040
916
  @thread.join if @thread && sync
1041
917
  end
1042
918
 
1043
- def begin_restart
919
+ def begin_restart(sync=false)
1044
920
  notify_safely(RESTART_COMMAND)
921
+ @thread.join if @thread && sync
1045
922
  end
1046
923
 
1047
924
  def fast_write(io, str)
@@ -1079,5 +956,13 @@ module Puma
1079
956
  HTTP_INJECTION_REGEX =~ header_value.to_s
1080
957
  end
1081
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
1082
967
  end
1083
968
  end