puma 5.6.5 → 6.1.1

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +165 -11
  3. data/README.md +22 -17
  4. data/bin/puma-wild +1 -1
  5. data/docs/compile_options.md +34 -0
  6. data/docs/fork_worker.md +1 -3
  7. data/docs/nginx.md +1 -1
  8. data/docs/systemd.md +1 -2
  9. data/docs/testing_benchmarks_local_files.md +150 -0
  10. data/docs/testing_test_rackup_ci_files.md +36 -0
  11. data/ext/puma_http11/extconf.rb +11 -8
  12. data/ext/puma_http11/http11_parser.c +1 -1
  13. data/ext/puma_http11/http11_parser.h +1 -1
  14. data/ext/puma_http11/http11_parser.java.rl +2 -2
  15. data/ext/puma_http11/http11_parser.rl +2 -2
  16. data/ext/puma_http11/http11_parser_common.rl +2 -2
  17. data/ext/puma_http11/mini_ssl.c +36 -15
  18. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  19. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
  20. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +156 -53
  21. data/ext/puma_http11/puma_http11.c +17 -9
  22. data/lib/puma/app/status.rb +3 -3
  23. data/lib/puma/binder.rb +40 -45
  24. data/lib/puma/cli.rb +11 -17
  25. data/lib/puma/client.rb +54 -16
  26. data/lib/puma/cluster/worker.rb +18 -11
  27. data/lib/puma/cluster/worker_handle.rb +4 -1
  28. data/lib/puma/cluster.rb +33 -30
  29. data/lib/puma/configuration.rb +75 -58
  30. data/lib/puma/const.rb +76 -88
  31. data/lib/puma/control_cli.rb +3 -6
  32. data/lib/puma/detect.rb +4 -0
  33. data/lib/puma/dsl.rb +110 -52
  34. data/lib/puma/error_logger.rb +17 -9
  35. data/lib/puma/events.rb +6 -126
  36. data/lib/puma/io_buffer.rb +39 -4
  37. data/lib/puma/jruby_restart.rb +2 -1
  38. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  39. data/lib/puma/launcher.rb +100 -175
  40. data/lib/puma/log_writer.rb +141 -0
  41. data/lib/puma/minissl/context_builder.rb +23 -12
  42. data/lib/puma/minissl.rb +82 -11
  43. data/lib/puma/plugin/systemd.rb +90 -0
  44. data/lib/puma/plugin/tmp_restart.rb +1 -1
  45. data/lib/puma/rack/builder.rb +4 -4
  46. data/lib/puma/rack_default.rb +19 -4
  47. data/lib/puma/reactor.rb +4 -4
  48. data/lib/puma/request.rb +344 -165
  49. data/lib/puma/runner.rb +52 -20
  50. data/lib/puma/sd_notify.rb +149 -0
  51. data/lib/puma/server.rb +57 -71
  52. data/lib/puma/single.rb +13 -11
  53. data/lib/puma/state_file.rb +1 -4
  54. data/lib/puma/thread_pool.rb +16 -16
  55. data/lib/puma/util.rb +0 -11
  56. data/lib/puma.rb +12 -11
  57. data/lib/rack/handler/puma.rb +115 -94
  58. metadata +10 -5
  59. data/lib/puma/queue_close.rb +0 -26
  60. data/lib/puma/systemd.rb +0 -46
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'thread'
4
4
 
5
+ require_relative 'io_buffer'
6
+
5
7
  module Puma
6
8
  # Internal Docs for A simple thread pool management object.
7
9
  #
@@ -29,7 +31,7 @@ module Puma
29
31
  # The block passed is the work that will be performed in each
30
32
  # thread.
31
33
  #
32
- def initialize(name, min, max, *extra, &block)
34
+ def initialize(name, options = {}, &block)
33
35
  @not_empty = ConditionVariable.new
34
36
  @not_full = ConditionVariable.new
35
37
  @mutex = Mutex.new
@@ -40,10 +42,13 @@ module Puma
40
42
  @waiting = 0
41
43
 
42
44
  @name = name
43
- @min = Integer(min)
44
- @max = Integer(max)
45
+ @min = Integer(options[:min_threads])
46
+ @max = Integer(options[:max_threads])
45
47
  @block = block
46
- @extra = extra
48
+ @out_of_band = options[:out_of_band]
49
+ @clean_thread_locals = options[:clean_thread_locals]
50
+ @reaping_time = options[:reaping_time]
51
+ @auto_trim_time = options[:auto_trim_time]
47
52
 
48
53
  @shutdown = false
49
54
 
@@ -62,14 +67,11 @@ module Puma
62
67
  end
63
68
  end
64
69
 
65
- @clean_thread_locals = false
66
70
  @force_shutdown = false
67
71
  @shutdown_mutex = Mutex.new
68
72
  end
69
73
 
70
74
  attr_reader :spawned, :trim_requested, :waiting
71
- attr_accessor :clean_thread_locals
72
- attr_accessor :out_of_band_hook # @version 5.0.0
73
75
 
74
76
  def self.clean_thread_locals
75
77
  Thread.current.keys.each do |key| # rubocop: disable Style/HashEachMethods
@@ -109,8 +111,6 @@ module Puma
109
111
  not_empty = @not_empty
110
112
  not_full = @not_full
111
113
 
112
- extra = @extra.map { |i| i.new }
113
-
114
114
  while true
115
115
  work = nil
116
116
 
@@ -144,7 +144,7 @@ module Puma
144
144
  end
145
145
 
146
146
  begin
147
- @out_of_band_pending = true if block.call(work, *extra)
147
+ @out_of_band_pending = true if block.call(work)
148
148
  rescue Exception => e
149
149
  STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
150
150
  end
@@ -160,12 +160,12 @@ module Puma
160
160
 
161
161
  # @version 5.0.0
162
162
  def trigger_out_of_band_hook
163
- return false unless out_of_band_hook && out_of_band_hook.any?
163
+ return false unless @out_of_band&.any?
164
164
 
165
165
  # we execute on idle hook when all threads are free
166
166
  return false unless @spawned == @waiting
167
167
 
168
- out_of_band_hook.each(&:call)
168
+ @out_of_band.each(&:call)
169
169
  true
170
170
  rescue Exception => e
171
171
  STDERR.puts "Exception calling out_of_band_hook: #{e.message} (#{e.class})"
@@ -319,12 +319,12 @@ module Puma
319
319
  end
320
320
  end
321
321
 
322
- def auto_trim!(timeout=30)
322
+ def auto_trim!(timeout=@auto_trim_time)
323
323
  @auto_trim = Automaton.new(self, timeout, "#{@name} threadpool trimmer", :trim)
324
324
  @auto_trim.start!
325
325
  end
326
326
 
327
- def auto_reap!(timeout=5)
327
+ def auto_reap!(timeout=@reaping_time)
328
328
  @reaper = Automaton.new(self, timeout, "#{@name} threadpool reaper", :reap)
329
329
  @reaper.start!
330
330
  end
@@ -354,8 +354,8 @@ module Puma
354
354
  @not_empty.broadcast
355
355
  @not_full.broadcast
356
356
 
357
- @auto_trim.stop if @auto_trim
358
- @reaper.stop if @reaper
357
+ @auto_trim&.stop
358
+ @reaper&.stop
359
359
  # dup workers so that we join them all safely
360
360
  @workers.dup
361
361
  end
data/lib/puma/util.rb CHANGED
@@ -39,17 +39,6 @@ module Puma
39
39
  end
40
40
  module_function :unescape, :escape
41
41
 
42
- # @version 5.0.0
43
- def nakayoshi_gc(events)
44
- events.log "! Promoting existing objects to old generation..."
45
- 4.times { GC.start(full_mark: false) }
46
- if GC.respond_to?(:compact)
47
- events.log "! Compacting..."
48
- GC.compact
49
- end
50
- events.log "! Friendly fork preparation complete."
51
- end
52
-
53
42
  DEFAULT_SEP = /[&;] */n
54
43
 
55
44
  # Stolen from Mongrel, with some small modifications:
data/lib/puma.rb CHANGED
@@ -3,30 +3,33 @@
3
3
  # Standard libraries
4
4
  require 'socket'
5
5
  require 'tempfile'
6
- require 'time'
7
- require 'etc'
8
6
  require 'uri'
9
7
  require 'stringio'
10
8
 
11
9
  require 'thread'
12
10
 
11
+ # use require, see https://github.com/puma/puma/pull/2381
13
12
  require 'puma/puma_http11'
14
- require 'puma/detect'
15
- require 'puma/json_serialization'
13
+
14
+ require_relative 'puma/detect'
15
+ require_relative 'puma/json_serialization'
16
16
 
17
17
  module Puma
18
- autoload :Const, 'puma/const'
19
- autoload :Server, 'puma/server'
20
- autoload :Launcher, 'puma/launcher'
18
+ # when Puma is loaded via `Puma::CLI`, all files are loaded via
19
+ # `require_relative`. The below are for non-standard loading
20
+ autoload :Const, "#{__dir__}/puma/const"
21
+ autoload :Server, "#{__dir__}/puma/server"
22
+ autoload :Launcher, "#{__dir__}/puma/launcher"
23
+ autoload :LogWriter, "#{__dir__}/puma/log_writer"
21
24
 
22
25
  # at present, MiniSSL::Engine is only defined in extension code (puma_http11),
23
26
  # not in minissl.rb
24
27
  HAS_SSL = const_defined?(:MiniSSL, false) && MiniSSL.const_defined?(:Engine, false)
25
28
 
26
- HAS_UNIX_SOCKET = Object.const_defined? :UNIXSocket
29
+ HAS_UNIX_SOCKET = Object.const_defined?(:UNIXSocket) && !IS_WINDOWS
27
30
 
28
31
  if HAS_SSL
29
- require 'puma/minissl'
32
+ require_relative 'puma/minissl'
30
33
  else
31
34
  module MiniSSL
32
35
  # this class is defined so that it exists when Puma is compiled
@@ -69,9 +72,7 @@ module Puma
69
72
  @get_stats.stats
70
73
  end
71
74
 
72
- # Thread name is new in Ruby 2.3
73
75
  def self.set_thread_name(name)
74
- return unless Thread.current.respond_to?(:name=)
75
76
  Thread.current.name = "puma #{name}"
76
77
  end
77
78
  end
@@ -1,114 +1,135 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/handler'
4
-
5
- module Rack
6
- module Handler
7
- module Puma
8
- DEFAULT_OPTIONS = {
9
- :Verbose => false,
10
- :Silent => false
11
- }
12
-
13
- def self.config(app, options = {})
14
- require 'puma'
15
- require 'puma/configuration'
16
- require 'puma/events'
17
- require 'puma/launcher'
18
-
19
- default_options = DEFAULT_OPTIONS.dup
20
-
21
- # Libraries pass in values such as :Port and there is no way to determine
22
- # if it is a default provided by the library or a special value provided
23
- # by the user. A special key `user_supplied_options` can be passed. This
24
- # contains an array of all explicitly defined user options. We then
25
- # know that all other values are defaults
26
- if user_supplied_options = options.delete(:user_supplied_options)
27
- (options.keys - user_supplied_options).each do |k|
28
- default_options[k] = options.delete(k)
29
- end
3
+ # This module is used as an 'include' file in code at bottom of file
4
+ module Puma
5
+ module RackHandler
6
+ DEFAULT_OPTIONS = {
7
+ :Verbose => false,
8
+ :Silent => false
9
+ }
10
+
11
+ def config(app, options = {})
12
+ require_relative '../../puma'
13
+ require_relative '../../puma/configuration'
14
+ require_relative '../../puma/log_writer'
15
+ require_relative '../../puma/launcher'
16
+
17
+ default_options = DEFAULT_OPTIONS.dup
18
+
19
+ # Libraries pass in values such as :Port and there is no way to determine
20
+ # if it is a default provided by the library or a special value provided
21
+ # by the user. A special key `user_supplied_options` can be passed. This
22
+ # contains an array of all explicitly defined user options. We then
23
+ # know that all other values are defaults
24
+ if user_supplied_options = options.delete(:user_supplied_options)
25
+ (options.keys - user_supplied_options).each do |k|
26
+ default_options[k] = options.delete(k)
30
27
  end
28
+ end
31
29
 
32
- conf = ::Puma::Configuration.new(options, default_options) do |user_config, file_config, default_config|
33
- if options.delete(:Verbose)
34
- require 'rack/common_logger'
35
- app = Rack::CommonLogger.new(app, STDOUT)
36
- end
37
-
38
- if options[:environment]
39
- user_config.environment options[:environment]
40
- end
41
-
42
- if options[:Threads]
43
- min, max = options.delete(:Threads).split(':', 2)
44
- user_config.threads min, max
45
- end
46
-
47
- if options[:Host] || options[:Port]
48
- host = options[:Host] || default_options[:Host]
49
- port = options[:Port] || default_options[:Port]
50
- self.set_host_port_to_config(host, port, user_config)
51
- end
52
-
53
- if default_options[:Host]
54
- file_config.set_default_host(default_options[:Host])
55
- end
56
- self.set_host_port_to_config(default_options[:Host], default_options[:Port], default_config)
57
-
58
- user_config.app app
30
+ conf = ::Puma::Configuration.new(options, default_options) do |user_config, file_config, default_config|
31
+ if options.delete(:Verbose)
32
+ require 'rack/common_logger'
33
+ app = Rack::CommonLogger.new(app, STDOUT)
59
34
  end
60
- conf
61
- end
62
35
 
63
- def self.run(app, **options)
64
- conf = self.config(app, options)
36
+ if options[:environment]
37
+ user_config.environment options[:environment]
38
+ end
65
39
 
66
- events = options.delete(:Silent) ? ::Puma::Events.strings : ::Puma::Events.stdio
40
+ if options[:Threads]
41
+ min, max = options.delete(:Threads).split(':', 2)
42
+ user_config.threads min, max
43
+ end
67
44
 
68
- launcher = ::Puma::Launcher.new(conf, :events => events)
45
+ if options[:Host] || options[:Port]
46
+ host = options[:Host] || default_options[:Host]
47
+ port = options[:Port] || default_options[:Port]
48
+ self.set_host_port_to_config(host, port, user_config)
49
+ end
69
50
 
70
- yield launcher if block_given?
71
- begin
72
- launcher.run
73
- rescue Interrupt
74
- puts "* Gracefully stopping, waiting for requests to finish"
75
- launcher.stop
76
- puts "* Goodbye!"
51
+ if default_options[:Host]
52
+ file_config.set_default_host(default_options[:Host])
77
53
  end
54
+ self.set_host_port_to_config(default_options[:Host], default_options[:Port], default_config)
55
+
56
+ user_config.app app
78
57
  end
58
+ conf
59
+ end
60
+
61
+ def run(app, **options)
62
+ conf = self.config(app, options)
63
+
64
+ log_writer = options.delete(:Silent) ? ::Puma::LogWriter.strings : ::Puma::LogWriter.stdio
65
+
66
+ launcher = ::Puma::Launcher.new(conf, :log_writer => log_writer)
79
67
 
80
- def self.valid_options
81
- {
82
- "Host=HOST" => "Hostname to listen on (default: localhost)",
83
- "Port=PORT" => "Port to listen on (default: 8080)",
84
- "Threads=MIN:MAX" => "min:max threads to use (default 0:16)",
85
- "Verbose" => "Don't report each request (default: false)"
86
- }
68
+ yield launcher if block_given?
69
+ begin
70
+ launcher.run
71
+ rescue Interrupt
72
+ puts "* Gracefully stopping, waiting for requests to finish"
73
+ launcher.stop
74
+ puts "* Goodbye!"
87
75
  end
76
+ end
88
77
 
89
- def self.set_host_port_to_config(host, port, config)
90
- config.clear_binds! if host || port
91
-
92
- if host && (host[0,1] == '.' || host[0,1] == '/')
93
- config.bind "unix://#{host}"
94
- elsif host && host =~ /^ssl:\/\//
95
- uri = URI.parse(host)
96
- uri.port ||= port || ::Puma::Configuration::DefaultTCPPort
97
- config.bind uri.to_s
98
- else
99
-
100
- if host
101
- port ||= ::Puma::Configuration::DefaultTCPPort
102
- end
103
-
104
- if port
105
- host ||= ::Puma::Configuration::DefaultTCPHost
106
- config.port port, host
107
- end
78
+ def valid_options
79
+ {
80
+ "Host=HOST" => "Hostname to listen on (default: localhost)",
81
+ "Port=PORT" => "Port to listen on (default: 8080)",
82
+ "Threads=MIN:MAX" => "min:max threads to use (default 0:16)",
83
+ "Verbose" => "Don't report each request (default: false)"
84
+ }
85
+ end
86
+
87
+ def set_host_port_to_config(host, port, config)
88
+ config.clear_binds! if host || port
89
+
90
+ if host && (host[0,1] == '.' || host[0,1] == '/')
91
+ config.bind "unix://#{host}"
92
+ elsif host && host =~ /^ssl:\/\//
93
+ uri = URI.parse(host)
94
+ uri.port ||= port || ::Puma::Configuration::DEFAULTS[:tcp_port]
95
+ config.bind uri.to_s
96
+ else
97
+
98
+ if host
99
+ port ||= ::Puma::Configuration::DEFAULTS[:tcp_port]
100
+ end
101
+
102
+ if port
103
+ host ||= ::Puma::Configuration::DEFAULTS[:tcp_host]
104
+ config.port port, host
108
105
  end
109
106
  end
110
107
  end
108
+ end
109
+ end
111
110
 
112
- register :puma, Puma
111
+ # rackup was removed in Rack 3, it is now a separate gem
112
+ if Object.const_defined? :Rackup
113
+ module Rackup
114
+ module Handler
115
+ module Puma
116
+ class << self
117
+ include ::Puma::RackHandler
118
+ end
119
+ end
120
+ register :puma, Puma
121
+ end
122
+ end
123
+ else
124
+ do_register = Object.const_defined?(:Rack) && Rack::RELEASE < '3'
125
+ module Rack
126
+ module Handler
127
+ module Puma
128
+ class << self
129
+ include ::Puma::RackHandler
130
+ end
131
+ end
132
+ end
113
133
  end
134
+ ::Rack::Handler.register(:puma, ::Rack::Handler::Puma) if do_register
114
135
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puma
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.6.5
4
+ version: 6.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Phoenix
@@ -62,6 +62,8 @@ files:
62
62
  - docs/signals.md
63
63
  - docs/stats.md
64
64
  - docs/systemd.md
65
+ - docs/testing_benchmarks_local_files.md
66
+ - docs/testing_test_rackup_ci_files.md
65
67
  - ext/puma_http11/PumaHttp11Service.java
66
68
  - ext/puma_http11/ext_help.h
67
69
  - ext/puma_http11/extconf.rb
@@ -96,22 +98,24 @@ files:
96
98
  - lib/puma/jruby_restart.rb
97
99
  - lib/puma/json_serialization.rb
98
100
  - lib/puma/launcher.rb
101
+ - lib/puma/launcher/bundle_pruner.rb
102
+ - lib/puma/log_writer.rb
99
103
  - lib/puma/minissl.rb
100
104
  - lib/puma/minissl/context_builder.rb
101
105
  - lib/puma/null_io.rb
102
106
  - lib/puma/plugin.rb
107
+ - lib/puma/plugin/systemd.rb
103
108
  - lib/puma/plugin/tmp_restart.rb
104
- - lib/puma/queue_close.rb
105
109
  - lib/puma/rack/builder.rb
106
110
  - lib/puma/rack/urlmap.rb
107
111
  - lib/puma/rack_default.rb
108
112
  - lib/puma/reactor.rb
109
113
  - lib/puma/request.rb
110
114
  - lib/puma/runner.rb
115
+ - lib/puma/sd_notify.rb
111
116
  - lib/puma/server.rb
112
117
  - lib/puma/single.rb
113
118
  - lib/puma/state_file.rb
114
- - lib/puma/systemd.rb
115
119
  - lib/puma/thread_pool.rb
116
120
  - lib/puma/util.rb
117
121
  - lib/rack/handler/puma.rb
@@ -125,6 +129,7 @@ metadata:
125
129
  changelog_uri: https://github.com/puma/puma/blob/master/History.md
126
130
  homepage_uri: https://puma.io
127
131
  source_code_uri: https://github.com/puma/puma
132
+ rubygems_mfa_required: 'true'
128
133
  post_install_message:
129
134
  rdoc_options: []
130
135
  require_paths:
@@ -133,14 +138,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
133
138
  requirements:
134
139
  - - ">="
135
140
  - !ruby/object:Gem::Version
136
- version: '2.2'
141
+ version: '2.4'
137
142
  required_rubygems_version: !ruby/object:Gem::Requirement
138
143
  requirements:
139
144
  - - ">="
140
145
  - !ruby/object:Gem::Version
141
146
  version: '0'
142
147
  requirements: []
143
- rubygems_version: 3.2.26
148
+ rubygems_version: 3.3.20
144
149
  signing_key:
145
150
  specification_version: 4
146
151
  summary: Puma is a simple, fast, threaded, and highly parallel HTTP 1.1 server for
@@ -1,26 +0,0 @@
1
- class ClosedQueueError < StandardError; end
2
- module Puma
3
-
4
- # Queue#close was added in Ruby 2.3.
5
- # Add a simple implementation for earlier Ruby versions.
6
- #
7
- module QueueClose
8
- def close
9
- num_waiting.times {push nil}
10
- @closed = true
11
- end
12
- def closed?
13
- @closed ||= false
14
- end
15
- def push(object)
16
- raise ClosedQueueError if closed?
17
- super
18
- end
19
- alias << push
20
- def pop(non_block=false)
21
- return nil if !non_block && closed? && empty?
22
- super
23
- end
24
- end
25
- ::Queue.prepend QueueClose
26
- end
data/lib/puma/systemd.rb DELETED
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'sd_notify'
4
-
5
- module Puma
6
- class Systemd
7
- def initialize(events)
8
- @events = events
9
- end
10
-
11
- def hook_events
12
- @events.on_booted { SdNotify.ready }
13
- @events.on_stopped { SdNotify.stopping }
14
- @events.on_restart { SdNotify.reloading }
15
- end
16
-
17
- def start_watchdog
18
- return unless SdNotify.watchdog?
19
-
20
- ping_f = watchdog_sleep_time
21
-
22
- log "Pinging systemd watchdog every #{ping_f.round(1)} sec"
23
- Thread.new do
24
- loop do
25
- sleep ping_f
26
- SdNotify.watchdog
27
- end
28
- end
29
- end
30
-
31
- private
32
-
33
- def watchdog_sleep_time
34
- usec = Integer(ENV["WATCHDOG_USEC"])
35
-
36
- sec_f = usec / 1_000_000.0
37
- # "It is recommended that a daemon sends a keep-alive notification message
38
- # to the service manager every half of the time returned here."
39
- sec_f / 2
40
- end
41
-
42
- def log(str)
43
- @events.log str
44
- end
45
- end
46
- end