puma 6.6.0-java → 7.1.0-java

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +170 -5
  3. data/README.md +24 -32
  4. data/docs/fork_worker.md +5 -5
  5. data/docs/kubernetes.md +8 -6
  6. data/docs/restart.md +2 -2
  7. data/docs/signals.md +11 -11
  8. data/docs/stats.md +3 -2
  9. data/docs/systemd.md +1 -1
  10. data/ext/puma_http11/extconf.rb +2 -17
  11. data/ext/puma_http11/mini_ssl.c +18 -8
  12. data/ext/puma_http11/org/jruby/puma/Http11.java +10 -2
  13. data/ext/puma_http11/puma_http11.c +23 -11
  14. data/lib/puma/binder.rb +10 -8
  15. data/lib/puma/cli.rb +3 -5
  16. data/lib/puma/client.rb +95 -61
  17. data/lib/puma/cluster/worker.rb +9 -10
  18. data/lib/puma/cluster/worker_handle.rb +38 -7
  19. data/lib/puma/cluster.rb +41 -26
  20. data/lib/puma/cluster_accept_loop_delay.rb +91 -0
  21. data/lib/puma/commonlogger.rb +3 -3
  22. data/lib/puma/configuration.rb +89 -43
  23. data/lib/puma/const.rb +9 -10
  24. data/lib/puma/control_cli.rb +6 -2
  25. data/lib/puma/detect.rb +2 -0
  26. data/lib/puma/dsl.rb +135 -94
  27. data/lib/puma/error_logger.rb +3 -1
  28. data/lib/puma/events.rb +25 -10
  29. data/lib/puma/io_buffer.rb +8 -4
  30. data/lib/puma/launcher/bundle_pruner.rb +1 -1
  31. data/lib/puma/launcher.rb +52 -48
  32. data/lib/puma/minissl.rb +0 -1
  33. data/lib/puma/plugin/systemd.rb +3 -3
  34. data/lib/puma/puma_http11.jar +0 -0
  35. data/lib/puma/rack/urlmap.rb +1 -1
  36. data/lib/puma/reactor.rb +19 -4
  37. data/lib/puma/request.rb +45 -32
  38. data/lib/puma/runner.rb +8 -17
  39. data/lib/puma/server.rb +111 -61
  40. data/lib/puma/single.rb +5 -2
  41. data/lib/puma/state_file.rb +3 -2
  42. data/lib/puma/thread_pool.rb +47 -82
  43. data/lib/puma/util.rb +0 -7
  44. data/lib/puma.rb +10 -0
  45. data/lib/rack/handler/puma.rb +2 -2
  46. data/tools/Dockerfile +3 -1
  47. metadata +6 -7
data/lib/puma/launcher.rb CHANGED
@@ -22,12 +22,15 @@ module Puma
22
22
  #
23
23
  # +conf+ A Puma::Configuration object indicating how to run the server.
24
24
  #
25
- # +launcher_args+ A Hash that currently has one required key `:events`,
26
- # this is expected to hold an object similar to an `Puma::LogWriter.stdio`,
27
- # this object will be responsible for broadcasting Puma's internal state
28
- # to a logging destination. An optional key `:argv` can be supplied,
29
- # this should be an array of strings, these arguments are re-used when
30
- # restarting the puma server.
25
+ # +launcher_args+ A Hash that has a few optional keys.
26
+ # - +:log_writer+:: Expected to hold an object similar to `Puma::LogWriter.stdio`.
27
+ # This object will be responsible for broadcasting Puma's internal state
28
+ # to a logging destination.
29
+ # - +:events+:: Expected to hold an object similar to `Puma::Events`.
30
+ # - +:argv+:: Expected to be an array of strings.
31
+ # - +:env+:: Expected to hold a hash of environment variables.
32
+ #
33
+ # These arguments are re-used when restarting the puma server.
31
34
  #
32
35
  # Examples:
33
36
  #
@@ -39,27 +42,43 @@ module Puma
39
42
  # end
40
43
  # Puma::Launcher.new(conf, log_writer: Puma::LogWriter.stdio).run
41
44
  def initialize(conf, launcher_args={})
42
- @runner = nil
43
- @log_writer = launcher_args[:log_writer] || LogWriter::DEFAULT
44
- @events = launcher_args[:events] || Events.new
45
- @argv = launcher_args[:argv] || []
45
+ ## Minimal initialization for a potential early restart (e.g. when pruning bundle)
46
+
47
+ @config = conf
48
+ @config.clamp
49
+
50
+ @options = @config.options
51
+
52
+ @log_writer = launcher_args[:log_writer] || LogWriter::DEFAULT
53
+ @log_writer.formatter = LogWriter::PidFormatter.new if clustered?
54
+ @log_writer.formatter = @options[:log_formatter] if @options[:log_formatter]
55
+ @log_writer.custom_logger = @options[:custom_logger] if @options[:custom_logger]
56
+ @options[:log_writer] = @log_writer
57
+ @options[:logger] = @log_writer if clustered?
58
+
59
+ @events = launcher_args[:events] || Events.new
60
+
61
+ @argv = launcher_args[:argv] || []
46
62
  @original_argv = @argv.dup
47
- @config = conf
48
63
 
49
- env = launcher_args.delete(:env) || ENV
64
+ ## End minimal initialization
50
65
 
51
- @config.options[:log_writer] = @log_writer
66
+ generate_restart_data
67
+ Dir.chdir(@restart_dir)
68
+
69
+ prune_bundler!
70
+
71
+ env = launcher_args.delete(:env) || ENV
52
72
 
53
73
  # Advertise the Configuration
54
74
  Puma.cli_config = @config if defined?(Puma.cli_config)
75
+ log_config if env['PUMA_LOG_CONFIG']
55
76
 
56
- @config.load
57
-
58
- @binder = Binder.new(@log_writer, conf)
59
- @binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
60
- @binder.create_activated_fds(ENV).each { |k| ENV.delete k }
77
+ @binder = Binder.new(@log_writer, @options)
78
+ @binder.create_inherited_fds(env).each { |k| env.delete k }
79
+ @binder.create_activated_fds(env).each { |k| env.delete k }
61
80
 
62
- @environment = conf.environment
81
+ @environment = @config.environment
63
82
 
64
83
  # Load the systemd integration if we detect systemd's NOTIFY_SOCKET.
65
84
  # Skip this on JRuby though, because it is incompatible with the systemd
@@ -68,37 +87,21 @@ module Puma
68
87
  @config.plugins.create('systemd')
69
88
  end
70
89
 
71
- if @config.options[:bind_to_activated_sockets]
72
- @config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
73
- @config.options[:binds],
74
- @config.options[:bind_to_activated_sockets] == 'only'
90
+ if @options[:bind_to_activated_sockets]
91
+ @options[:binds] = @binder.synthesize_binds_from_activated_fs(
92
+ @options[:binds],
93
+ @options[:bind_to_activated_sockets] == 'only'
75
94
  )
76
95
  end
77
96
 
78
- @options = @config.options
79
- @config.clamp
80
-
81
- @log_writer.formatter = LogWriter::PidFormatter.new if clustered?
82
- @log_writer.formatter = options[:log_formatter] if @options[:log_formatter]
83
-
84
- @log_writer.custom_logger = options[:custom_logger] if @options[:custom_logger]
85
-
86
- generate_restart_data
87
-
88
97
  if clustered? && !Puma.forkable?
89
98
  unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
90
99
  end
91
100
 
92
- Dir.chdir(@restart_dir)
93
-
94
- prune_bundler!
95
-
96
101
  @environment = @options[:environment] if @options[:environment]
97
102
  set_rack_environment
98
103
 
99
104
  if clustered?
100
- @options[:logger] = @log_writer
101
-
102
105
  @runner = Cluster.new(self)
103
106
  else
104
107
  @runner = Single.new(self)
@@ -106,8 +109,6 @@ module Puma
106
109
  Puma.stats_object = @runner
107
110
 
108
111
  @status = :run
109
-
110
- log_config if env['PUMA_LOG_CONFIG']
111
112
  end
112
113
 
113
114
  attr_reader :binder, :log_writer, :events, :config, :options, :restart_dir
@@ -140,7 +141,10 @@ module Puma
140
141
  # Delete the configured pidfile
141
142
  def delete_pidfile
142
143
  path = @options[:pidfile]
143
- File.unlink(path) if path && File.exist?(path)
144
+ begin
145
+ File.unlink(path) if path
146
+ rescue Errno::ENOENT
147
+ end
144
148
  end
145
149
 
146
150
  # Begin async shutdown of the server
@@ -277,7 +281,7 @@ module Puma
277
281
  end
278
282
 
279
283
  def do_graceful_stop
280
- @events.fire_on_stopped!
284
+ @events.fire_after_stopped!
281
285
  @runner.stop_blocked
282
286
  end
283
287
 
@@ -289,8 +293,8 @@ module Puma
289
293
  end
290
294
 
291
295
  def restart!
292
- @events.fire_on_restart!
293
- @config.run_hooks :on_restart, self, @log_writer
296
+ @events.fire_before_restart!
297
+ @config.run_hooks :before_restart, self, @log_writer
294
298
 
295
299
  if Puma.jruby?
296
300
  close_binder_listeners
@@ -382,9 +386,9 @@ module Puma
382
386
  # using it.
383
387
  @restart_dir = Dir.pwd
384
388
 
385
- # Use the same trick as unicorn, namely favor PWD because
386
- # it will contain an unresolved symlink, useful for when
387
- # the pwd is /data/releases/current.
389
+ # Use the same trick as unicorn, namely favor PWD because
390
+ # it will contain an unresolved symlink, useful for when
391
+ # the pwd is /data/releases/current.
388
392
  elsif dir = ENV['PWD']
389
393
  s_env = File.stat(dir)
390
394
  s_pwd = File.stat(Dir.pwd)
data/lib/puma/minissl.rb CHANGED
@@ -172,7 +172,6 @@ module Puma
172
172
  end
173
173
  end
174
174
  rescue IOError, SystemCallError
175
- Puma::Util.purge_interrupt_queue
176
175
  # nothing
177
176
  ensure
178
177
  @socket.close
@@ -14,9 +14,9 @@ Puma::Plugin.create do
14
14
  launcher.log_writer.log "* Enabling systemd notification integration"
15
15
 
16
16
  # hook_events
17
- launcher.events.on_booted { Puma::SdNotify.ready }
18
- launcher.events.on_stopped { Puma::SdNotify.stopping }
19
- launcher.events.on_restart { Puma::SdNotify.reloading }
17
+ launcher.events.after_booted { Puma::SdNotify.ready }
18
+ launcher.events.after_stopped { Puma::SdNotify.stopping }
19
+ launcher.events.before_restart { Puma::SdNotify.reloading }
20
20
 
21
21
  # start watchdog
22
22
  if Puma::SdNotify.watchdog?
Binary file
@@ -70,7 +70,7 @@ module Puma::Rack
70
70
  return app.call(env)
71
71
  end
72
72
 
73
- [404, {'Content-Type' => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
73
+ [404, {'content-type' => "text/plain", "x-cascade" => "pass"}, ["Not Found: #{path}"]]
74
74
 
75
75
  ensure
76
76
  env['PATH_INFO'] = path
data/lib/puma/reactor.rb CHANGED
@@ -15,6 +15,12 @@ module Puma
15
15
  #
16
16
  # The implementation uses a Queue to synchronize adding new objects from the internal select loop.
17
17
  class Reactor
18
+
19
+ # @!attribute [rw] reactor_max
20
+ # Maximum number of clients in the selector. Reset with calls to `Server.stats`.
21
+ attr_accessor :reactor_max
22
+ attr_reader :reactor_size
23
+
18
24
  # Create a new Reactor to monitor IO objects added by #add.
19
25
  # The provided block will be invoked when an IO has data available to read,
20
26
  # its timeout elapses, or when the Reactor shuts down.
@@ -29,6 +35,8 @@ module Puma
29
35
  @input = Queue.new
30
36
  @timeouts = []
31
37
  @block = block
38
+ @reactor_size = 0
39
+ @reactor_max = 0
32
40
  end
33
41
 
34
42
  # Run the internal select loop, using a background thread by default.
@@ -73,11 +81,15 @@ module Puma
73
81
  # Wakeup any registered object that receives incoming data.
74
82
  # Block until the earliest timeout or Selector#wakeup is called.
75
83
  timeout = (earliest = @timeouts.first) && earliest.timeout
76
- @selector.select(timeout) {|mon| wakeup!(mon.value)}
84
+ monitor_wake_up = false
85
+ @selector.select(timeout) do |monitor|
86
+ monitor_wake_up = true
87
+ wakeup!(monitor.value)
88
+ end
77
89
 
78
90
  # Wakeup all objects that timed out.
79
- timed_out = @timeouts.take_while {|t| t.timeout == 0}
80
- timed_out.each { |c| wakeup! c }
91
+ timed_out = @timeouts.take_while { |client| client.timeout == 0 }
92
+ timed_out.each { |client| wakeup!(client) }
81
93
 
82
94
  unless @input.empty?
83
95
  until @input.empty?
@@ -94,7 +106,7 @@ module Puma
94
106
  # NoMethodError may be rarely raised when calling @selector.select, which
95
107
  # is odd. Regardless, it may continue for thousands of calls if retried.
96
108
  # Also, when it raises, @selector.close also raises an error.
97
- if NoMethodError === e
109
+ if !monitor_wake_up && NoMethodError === e
98
110
  close_selector = false
99
111
  else
100
112
  retry
@@ -108,6 +120,8 @@ module Puma
108
120
  # Start monitoring the object.
109
121
  def register(client)
110
122
  @selector.register(client.to_io, :r).value = client
123
+ @reactor_size += 1
124
+ @reactor_max = @reactor_size if @reactor_max < @reactor_size
111
125
  @timeouts << client
112
126
  rescue ArgumentError
113
127
  # unreadable clients raise error when processed by NIO
@@ -118,6 +132,7 @@ module Puma
118
132
  def wakeup!(client)
119
133
  if @block.call client
120
134
  @selector.deregister client.to_io
135
+ @reactor_size -= 1
121
136
  @timeouts.delete client
122
137
  end
123
138
  end
data/lib/puma/request.rb CHANGED
@@ -52,6 +52,7 @@ module Puma
52
52
  io_buffer = client.io_buffer
53
53
  socket = client.io # io may be a MiniSSL::Socket
54
54
  app_body = nil
55
+ error = nil
55
56
 
56
57
  return false if closed_socket?(socket)
57
58
 
@@ -68,7 +69,7 @@ module Puma
68
69
  end
69
70
 
70
71
  env[HIJACK_P] = true
71
- env[HIJACK] = client
72
+ env[HIJACK] = client.method :full_hijack
72
73
 
73
74
  env[RACK_INPUT] = client.body
74
75
  env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
@@ -92,6 +93,7 @@ module Puma
92
93
  # array, we will invoke them when the request is done.
93
94
  #
94
95
  env[RACK_AFTER_REPLY] ||= []
96
+ env[RACK_RESPONSE_FINISHED] ||= []
95
97
 
96
98
  begin
97
99
  if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
@@ -119,15 +121,15 @@ module Puma
119
121
 
120
122
  return :async
121
123
  end
122
- rescue ThreadPool::ForceShutdown => e
123
- @log_writer.unknown_error e, client, "Rack app"
124
+ rescue ThreadPool::ForceShutdown => error
125
+ @log_writer.unknown_error error, client, "Rack app"
124
126
  @log_writer.log "Detected force shutdown of a thread"
125
127
 
126
- status, headers, res_body = lowlevel_error(e, env, 503)
127
- rescue Exception => e
128
- @log_writer.unknown_error e, client, "Rack app"
128
+ status, headers, res_body = lowlevel_error(error, env, 503)
129
+ rescue Exception => error
130
+ @log_writer.unknown_error error, client, "Rack app"
129
131
 
130
- status, headers, res_body = lowlevel_error(e, env, 500)
132
+ status, headers, res_body = lowlevel_error(error, env, 500)
131
133
  end
132
134
  prepare_response(status, headers, res_body, requests, client)
133
135
  ensure
@@ -135,12 +137,25 @@ module Puma
135
137
  uncork_socket client.io
136
138
  app_body.close if app_body.respond_to? :close
137
139
  client&.tempfile_close
138
- after_reply = env[RACK_AFTER_REPLY] || []
139
- begin
140
- after_reply.each { |o| o.call }
141
- rescue StandardError => e
142
- @log_writer.debug_error e
143
- end unless after_reply.empty?
140
+ if after_reply = env[RACK_AFTER_REPLY]
141
+ after_reply.each do |o|
142
+ begin
143
+ o.call
144
+ rescue StandardError => e
145
+ @log_writer.debug_error e
146
+ end
147
+ end
148
+ end
149
+
150
+ if response_finished = env[RACK_RESPONSE_FINISHED]
151
+ response_finished.reverse_each do |o|
152
+ begin
153
+ o.call(env, status, headers, error)
154
+ rescue StandardError => e
155
+ @log_writer.debug_error e
156
+ end
157
+ end
158
+ end
144
159
  end
145
160
 
146
161
  # Assembles the headers and prepares the body for actually sending the
@@ -161,17 +176,7 @@ module Puma
161
176
  return false if closed_socket?(socket)
162
177
 
163
178
  # Close the connection after a reasonable number of inline requests
164
- # if the server is at capacity and the listener has a new connection ready.
165
- # This allows Puma to service connections fairly when the number
166
- # of concurrent connections exceeds the size of the threadpool.
167
- force_keep_alive = if @enable_keep_alives
168
- requests < @max_fast_inline ||
169
- @thread_pool.busy_threads < @max_threads ||
170
- !client.listener.to_io.wait_readable(0)
171
- else
172
- # Always set force_keep_alive to false if the server has keep-alives not enabled.
173
- false
174
- end
179
+ force_keep_alive = @enable_keep_alives && client.requests_served < @max_keep_alive
175
180
 
176
181
  resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
177
182
 
@@ -191,7 +196,8 @@ module Puma
191
196
  elsif res_body.is_a?(File) && res_body.respond_to?(:size)
192
197
  body = res_body
193
198
  content_length = body.size
194
- elsif res_body.respond_to?(:to_path) && File.readable?(fn = res_body.to_path)
199
+ elsif res_body.respond_to?(:to_path) && (fn = res_body.to_path) &&
200
+ File.readable?(fn)
195
201
  body = File.open fn, 'rb'
196
202
  content_length = body.size
197
203
  close_body = true
@@ -199,7 +205,7 @@ module Puma
199
205
  body = res_body
200
206
  end
201
207
  elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) &&
202
- File.readable?(fn = res_body.to_path)
208
+ (fn = res_body.to_path) && File.readable?(fn = res_body.to_path)
203
209
  body = File.open fn, 'rb'
204
210
  content_length = body.size
205
211
  close_body = true
@@ -263,14 +269,21 @@ module Puma
263
269
 
264
270
  fast_write_response socket, body, io_buffer, chunked, content_length.to_i
265
271
  body.close if close_body
266
- keep_alive
272
+ # if we're shutting down, close keep_alive connections
273
+ !shutting_down? && keep_alive
267
274
  end
268
275
 
276
+ HTTP_ON_VALUES = { "on" => true, HTTPS => true }
277
+ private_constant :HTTP_ON_VALUES
278
+
269
279
  # @param env [Hash] see Puma::Client#env, from request
270
280
  # @return [Puma::Const::PORT_443,Puma::Const::PORT_80]
271
281
  #
272
282
  def default_server_port(env)
273
- if ['on', HTTPS].include?(env[HTTPS_KEY]) || env[HTTP_X_FORWARDED_PROTO].to_s[0...5] == HTTPS || env[HTTP_X_FORWARDED_SCHEME] == HTTPS || env[HTTP_X_FORWARDED_SSL] == "on"
283
+ if HTTP_ON_VALUES[env[HTTPS_KEY]] ||
284
+ env[HTTP_X_FORWARDED_PROTO]&.start_with?(HTTPS) ||
285
+ env[HTTP_X_FORWARDED_SCHEME] == HTTPS ||
286
+ env[HTTP_X_FORWARDED_SSL] == "on"
274
287
  PORT_443
275
288
  else
276
289
  PORT_80
@@ -474,7 +487,7 @@ module Puma
474
487
 
475
488
  # The legacy HTTP_VERSION header can be sent as a client header.
476
489
  # Rack v4 may remove using HTTP_VERSION. If so, remove this line.
477
- env[HTTP_VERSION] = env[SERVER_PROTOCOL]
490
+ env[HTTP_VERSION] = env[SERVER_PROTOCOL] if @env_set_http_version
478
491
  end
479
492
  private :normalize_env
480
493
 
@@ -581,7 +594,7 @@ module Puma
581
594
  # response body
582
595
  # @param io_buffer [Puma::IOBuffer] modified inn place
583
596
  # @param force_keep_alive [Boolean] 'anded' with keep_alive, based on system
584
- # status and `@max_fast_inline`
597
+ # status and `@max_keep_alive`
585
598
  # @return [Hash] resp_info
586
599
  # @version 5.0.3
587
600
  #
@@ -664,10 +677,10 @@ module Puma
664
677
  if ary
665
678
  ary.each do |v|
666
679
  next if illegal_header_value?(v)
667
- io_buffer.append k, colon, v, line_ending
680
+ io_buffer.append k.downcase, colon, v, line_ending
668
681
  end
669
682
  else
670
- io_buffer.append k, colon, line_ending
683
+ io_buffer.append k.downcase, colon, line_ending
671
684
  end
672
685
  end
673
686
 
data/lib/puma/runner.rb CHANGED
@@ -33,7 +33,6 @@ module Puma
33
33
  @wakeup.write PIPE_WAKEUP unless @wakeup.closed?
34
34
 
35
35
  rescue SystemCallError, IOError
36
- Puma::Util.purge_interrupt_queue
37
36
  end
38
37
 
39
38
  def development?
@@ -93,22 +92,6 @@ module Puma
93
92
  @control.binder.close_listeners if @control
94
93
  end
95
94
 
96
- # @!attribute [r] ruby_engine
97
- # @deprecated Use `RUBY_DESCRIPTION` instead
98
- def ruby_engine
99
- warn "Puma::Runner#ruby_engine is deprecated; use RUBY_DESCRIPTION instead. It will be removed in puma v7."
100
-
101
- if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
102
- "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
103
- else
104
- if defined?(RUBY_ENGINE_VERSION)
105
- "#{RUBY_ENGINE} #{RUBY_ENGINE_VERSION} - ruby #{RUBY_VERSION}"
106
- else
107
- "#{RUBY_ENGINE} #{RUBY_VERSION}"
108
- end
109
- end
110
- end
111
-
112
95
  def output_header(mode)
113
96
  min_t = @options[:min_threads]
114
97
  max_t = @options[:max_threads]
@@ -128,6 +111,14 @@ module Puma
128
111
  end
129
112
  end
130
113
 
114
+ def warn_ruby_mn_threads
115
+ return if !ENV.key?('RUBY_MN_THREADS')
116
+
117
+ log "! WARNING: Detected `RUBY_MN_THREADS=#{ENV['RUBY_MN_THREADS']}`"
118
+ log "! This setting is known to cause performance regressions with Puma."
119
+ log "! Consider disabling this environment variable: https://github.com/puma/puma/issues/3720"
120
+ end
121
+
131
122
  def redirected_io?
132
123
  @options[:redirect_stdout] || @options[:redirect_stderr]
133
124
  end