puma 6.6.0 → 8.0.2
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.
- checksums.yaml +4 -4
- data/History.md +309 -5
- data/README.md +41 -42
- data/docs/5.0-Upgrade.md +98 -0
- data/docs/6.0-Upgrade.md +56 -0
- data/docs/7.0-Upgrade.md +52 -0
- data/docs/8.0-Upgrade.md +100 -0
- data/docs/deployment.md +58 -23
- data/docs/fork_worker.md +5 -5
- data/docs/grpc.md +62 -0
- data/docs/images/favicon.svg +1 -0
- data/docs/images/running-puma.svg +1 -0
- data/docs/images/standard-logo.svg +1 -0
- data/docs/jungle/README.md +1 -1
- data/docs/kubernetes.md +11 -16
- data/docs/plugins.md +2 -2
- data/docs/restart.md +2 -2
- data/docs/signals.md +21 -21
- data/docs/stats.md +4 -3
- data/docs/systemd.md +4 -4
- data/ext/puma_http11/extconf.rb +2 -17
- data/ext/puma_http11/http11_parser.java.rl +51 -65
- data/ext/puma_http11/mini_ssl.c +18 -8
- data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +174 -102
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +71 -85
- data/ext/puma_http11/puma_http11.c +122 -118
- data/lib/puma/app/status.rb +10 -2
- data/lib/puma/binder.rb +12 -10
- data/lib/puma/cli.rb +4 -6
- data/lib/puma/client.rb +205 -131
- data/lib/puma/client_env.rb +171 -0
- data/lib/puma/cluster/worker.rb +17 -17
- data/lib/puma/cluster/worker_handle.rb +38 -7
- data/lib/puma/cluster.rb +44 -30
- data/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/lib/puma/commonlogger.rb +3 -3
- data/lib/puma/configuration.rb +173 -58
- data/lib/puma/const.rb +11 -11
- data/lib/puma/control_cli.rb +7 -3
- data/lib/puma/detect.rb +13 -0
- data/lib/puma/dsl.rb +225 -108
- data/lib/puma/error_logger.rb +3 -1
- data/lib/puma/events.rb +25 -10
- data/lib/puma/io_buffer.rb +8 -4
- data/lib/puma/launcher/bundle_pruner.rb +3 -5
- data/lib/puma/launcher.rb +57 -53
- data/lib/puma/log_writer.rb +8 -2
- data/lib/puma/minissl.rb +0 -1
- data/lib/puma/plugin/systemd.rb +3 -3
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/reactor.rb +19 -13
- data/lib/puma/{request.rb → response.rb} +56 -212
- data/lib/puma/runner.rb +9 -18
- data/lib/puma/server.rb +182 -97
- data/lib/puma/server_plugin_control.rb +32 -0
- data/lib/puma/single.rb +7 -4
- data/lib/puma/state_file.rb +3 -2
- data/lib/puma/thread_pool.rb +170 -99
- data/lib/puma/util.rb +0 -7
- data/lib/puma.rb +10 -0
- data/lib/rack/handler/puma.rb +3 -3
- data/tools/Dockerfile +15 -5
- metadata +19 -7
- data/ext/puma_http11/ext_help.h +0 -15
data/lib/puma/events.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Puma
|
|
4
4
|
|
|
5
5
|
# This is an event sink used by `Puma::Server` to handle
|
|
6
|
-
# lifecycle events such as :
|
|
6
|
+
# lifecycle events such as :after_booted, :before_restart, and :after_stopped.
|
|
7
7
|
# Using `Puma::DSL` it is possible to register callback hooks
|
|
8
8
|
# for each event type.
|
|
9
9
|
class Events
|
|
@@ -30,28 +30,43 @@ module Puma
|
|
|
30
30
|
h
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
def after_booted(&block)
|
|
34
|
+
register(:after_booted, &block)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def before_restart(&block)
|
|
38
|
+
register(:before_restart, &block)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def after_stopped(&block)
|
|
42
|
+
register(:after_stopped, &block)
|
|
43
|
+
end
|
|
44
|
+
|
|
33
45
|
def on_booted(&block)
|
|
34
|
-
|
|
46
|
+
Puma.deprecate_method_change :on_booted, __callee__, :after_booted
|
|
47
|
+
after_booted(&block)
|
|
35
48
|
end
|
|
36
49
|
|
|
37
50
|
def on_restart(&block)
|
|
38
|
-
|
|
51
|
+
Puma.deprecate_method_change :on_restart, __callee__, :before_restart
|
|
52
|
+
before_restart(&block)
|
|
39
53
|
end
|
|
40
54
|
|
|
41
55
|
def on_stopped(&block)
|
|
42
|
-
|
|
56
|
+
Puma.deprecate_method_change :on_stopped, __callee__, :after_stopped
|
|
57
|
+
after_stopped(&block)
|
|
43
58
|
end
|
|
44
59
|
|
|
45
|
-
def
|
|
46
|
-
fire(:
|
|
60
|
+
def fire_after_booted!
|
|
61
|
+
fire(:after_booted)
|
|
47
62
|
end
|
|
48
63
|
|
|
49
|
-
def
|
|
50
|
-
fire(:
|
|
64
|
+
def fire_before_restart!
|
|
65
|
+
fire(:before_restart)
|
|
51
66
|
end
|
|
52
67
|
|
|
53
|
-
def
|
|
54
|
-
fire(:
|
|
68
|
+
def fire_after_stopped!
|
|
69
|
+
fire(:after_stopped)
|
|
55
70
|
end
|
|
56
71
|
end
|
|
57
72
|
end
|
data/lib/puma/io_buffer.rb
CHANGED
|
@@ -34,13 +34,17 @@ module Puma
|
|
|
34
34
|
|
|
35
35
|
alias_method :clear, :reset
|
|
36
36
|
|
|
37
|
-
#
|
|
38
|
-
if
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
# Create an `IoBuffer#append` method that accepts multiple strings and writes them
|
|
38
|
+
if RUBY_ENGINE == 'truffleruby'
|
|
39
|
+
# truffleruby (24.2.1, like ruby 3.3.7)
|
|
40
|
+
# StringIO.new.write("a", "b") # => `write': wrong number of arguments (given 2, expected 1) (ArgumentError)
|
|
41
41
|
def append(*strs)
|
|
42
42
|
strs.each { |str| write str }
|
|
43
43
|
end
|
|
44
|
+
else
|
|
45
|
+
# Ruby 3+
|
|
46
|
+
# StringIO.new.write("a", "b") # => 2
|
|
47
|
+
alias_method :append, :write
|
|
44
48
|
end
|
|
45
49
|
end
|
|
46
50
|
end
|
|
@@ -28,16 +28,14 @@ module Puma
|
|
|
28
28
|
|
|
29
29
|
log '* Pruning Bundler environment'
|
|
30
30
|
home = ENV['GEM_HOME']
|
|
31
|
-
|
|
32
|
-
bundle_app_config = Bundler.original_env['BUNDLE_APP_CONFIG']
|
|
31
|
+
original_bundle_env = Bundler.original_env.select { |k, v| k.start_with?('BUNDLE_') && v }
|
|
33
32
|
|
|
34
33
|
with_unbundled_env do
|
|
35
34
|
ENV['GEM_HOME'] = home
|
|
36
|
-
ENV['BUNDLE_GEMFILE'] = bundle_gemfile
|
|
37
35
|
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
|
38
|
-
ENV[
|
|
36
|
+
original_bundle_env.each { |k, v| ENV[k] = v }
|
|
39
37
|
args = [Gem.ruby, puma_wild_path, '-I', dirs.join(':')] + @original_argv
|
|
40
|
-
#
|
|
38
|
+
# Defaults to true which breaks socket activation
|
|
41
39
|
args += [{:close_others => false}]
|
|
42
40
|
Kernel.exec(*args)
|
|
43
41
|
end
|
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
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
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,44 @@ 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
|
-
|
|
43
|
-
|
|
44
|
-
@
|
|
45
|
-
|
|
45
|
+
## Minimal initialization before potential early restart (e.g. from bundle pruning)
|
|
46
|
+
|
|
47
|
+
@config = conf
|
|
48
|
+
# Advertise the CLI Configuration before config files are loaded
|
|
49
|
+
Puma.cli_config = @config if defined?(Puma.cli_config)
|
|
50
|
+
@config.clamp
|
|
51
|
+
|
|
52
|
+
@options = @config.options
|
|
53
|
+
|
|
54
|
+
@log_writer = launcher_args[:log_writer] || LogWriter::DEFAULT
|
|
55
|
+
@log_writer.formatter = LogWriter::PidFormatter.new if clustered?
|
|
56
|
+
@log_writer.formatter = @options[:log_formatter] if @options[:log_formatter]
|
|
57
|
+
@log_writer.custom_logger = @options[:custom_logger] if @options[:custom_logger]
|
|
58
|
+
@options[:log_writer] = @log_writer
|
|
59
|
+
@options[:logger] = @log_writer if clustered?
|
|
60
|
+
|
|
61
|
+
@events = launcher_args[:events] || Events.new
|
|
62
|
+
|
|
63
|
+
@argv = launcher_args[:argv] || []
|
|
46
64
|
@original_argv = @argv.dup
|
|
47
|
-
@config = conf
|
|
48
65
|
|
|
49
|
-
|
|
66
|
+
## End minimal initialization
|
|
50
67
|
|
|
51
|
-
|
|
68
|
+
generate_restart_data
|
|
69
|
+
Dir.chdir(@restart_dir)
|
|
52
70
|
|
|
53
|
-
|
|
54
|
-
|
|
71
|
+
prune_bundler!
|
|
72
|
+
|
|
73
|
+
env = launcher_args.delete(:env) || ENV
|
|
55
74
|
|
|
56
|
-
|
|
75
|
+
# Log after prune_bundler! to avoid duplicate logging if a restart occurs
|
|
76
|
+
log_config if env['PUMA_LOG_CONFIG']
|
|
57
77
|
|
|
58
|
-
@binder
|
|
59
|
-
@binder.create_inherited_fds(
|
|
60
|
-
@binder.create_activated_fds(
|
|
78
|
+
@binder = Binder.new(@log_writer, @options)
|
|
79
|
+
@binder.create_inherited_fds(env).each { |k| env.delete k }
|
|
80
|
+
@binder.create_activated_fds(env).each { |k| env.delete k }
|
|
61
81
|
|
|
62
|
-
@environment =
|
|
82
|
+
@environment = @config.environment
|
|
63
83
|
|
|
64
84
|
# Load the systemd integration if we detect systemd's NOTIFY_SOCKET.
|
|
65
85
|
# Skip this on JRuby though, because it is incompatible with the systemd
|
|
@@ -68,37 +88,21 @@ module Puma
|
|
|
68
88
|
@config.plugins.create('systemd')
|
|
69
89
|
end
|
|
70
90
|
|
|
71
|
-
if @
|
|
72
|
-
@
|
|
73
|
-
@
|
|
74
|
-
@
|
|
91
|
+
if @options[:bind_to_activated_sockets]
|
|
92
|
+
@options[:binds] = @binder.synthesize_binds_from_activated_fs(
|
|
93
|
+
@options[:binds],
|
|
94
|
+
@options[:bind_to_activated_sockets] == 'only'
|
|
75
95
|
)
|
|
76
96
|
end
|
|
77
97
|
|
|
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
98
|
if clustered? && !Puma.forkable?
|
|
89
99
|
unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
|
|
90
100
|
end
|
|
91
101
|
|
|
92
|
-
Dir.chdir(@restart_dir)
|
|
93
|
-
|
|
94
|
-
prune_bundler!
|
|
95
|
-
|
|
96
102
|
@environment = @options[:environment] if @options[:environment]
|
|
97
103
|
set_rack_environment
|
|
98
104
|
|
|
99
105
|
if clustered?
|
|
100
|
-
@options[:logger] = @log_writer
|
|
101
|
-
|
|
102
106
|
@runner = Cluster.new(self)
|
|
103
107
|
else
|
|
104
108
|
@runner = Single.new(self)
|
|
@@ -106,8 +110,6 @@ module Puma
|
|
|
106
110
|
Puma.stats_object = @runner
|
|
107
111
|
|
|
108
112
|
@status = :run
|
|
109
|
-
|
|
110
|
-
log_config if env['PUMA_LOG_CONFIG']
|
|
111
113
|
end
|
|
112
114
|
|
|
113
115
|
attr_reader :binder, :log_writer, :events, :config, :options, :restart_dir
|
|
@@ -140,7 +142,10 @@ module Puma
|
|
|
140
142
|
# Delete the configured pidfile
|
|
141
143
|
def delete_pidfile
|
|
142
144
|
path = @options[:pidfile]
|
|
143
|
-
|
|
145
|
+
begin
|
|
146
|
+
File.unlink(path) if path
|
|
147
|
+
rescue Errno::ENOENT
|
|
148
|
+
end
|
|
144
149
|
end
|
|
145
150
|
|
|
146
151
|
# Begin async shutdown of the server
|
|
@@ -277,7 +282,7 @@ module Puma
|
|
|
277
282
|
end
|
|
278
283
|
|
|
279
284
|
def do_graceful_stop
|
|
280
|
-
@events.
|
|
285
|
+
@events.fire_after_stopped!
|
|
281
286
|
@runner.stop_blocked
|
|
282
287
|
end
|
|
283
288
|
|
|
@@ -289,8 +294,8 @@ module Puma
|
|
|
289
294
|
end
|
|
290
295
|
|
|
291
296
|
def restart!
|
|
292
|
-
@events.
|
|
293
|
-
@config.run_hooks :
|
|
297
|
+
@events.fire_before_restart!
|
|
298
|
+
@config.run_hooks :before_restart, self, @log_writer
|
|
294
299
|
|
|
295
300
|
if Puma.jruby?
|
|
296
301
|
close_binder_listeners
|
|
@@ -382,9 +387,9 @@ module Puma
|
|
|
382
387
|
# using it.
|
|
383
388
|
@restart_dir = Dir.pwd
|
|
384
389
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
390
|
+
# Use the same trick as unicorn, namely favor PWD because
|
|
391
|
+
# it will contain an unresolved symlink, useful for when
|
|
392
|
+
# the pwd is /data/releases/current.
|
|
388
393
|
elsif dir = ENV['PWD']
|
|
389
394
|
s_env = File.stat(dir)
|
|
390
395
|
s_pwd = File.stat(Dir.pwd)
|
|
@@ -471,8 +476,8 @@ module Puma
|
|
|
471
476
|
end
|
|
472
477
|
|
|
473
478
|
begin
|
|
474
|
-
|
|
475
|
-
Signal.trap
|
|
479
|
+
if Puma.backtrace_signal
|
|
480
|
+
Signal.trap Puma.backtrace_signal do
|
|
476
481
|
thread_status do |name, backtrace|
|
|
477
482
|
@log_writer.log(name)
|
|
478
483
|
@log_writer.log(backtrace.map { |bt| " #{bt}" })
|
|
@@ -480,8 +485,7 @@ module Puma
|
|
|
480
485
|
end
|
|
481
486
|
end
|
|
482
487
|
rescue Exception
|
|
483
|
-
|
|
484
|
-
# to see this constantly on Linux.
|
|
488
|
+
log "*** SIGINFO/SIGPWR not implemented, signal based backtrace unavailable!"
|
|
485
489
|
end
|
|
486
490
|
end
|
|
487
491
|
|
data/lib/puma/log_writer.rb
CHANGED
|
@@ -90,8 +90,14 @@ module Puma
|
|
|
90
90
|
@debug
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
-
def debug(str)
|
|
94
|
-
|
|
93
|
+
def debug(str=nil, &block)
|
|
94
|
+
if @debug
|
|
95
|
+
if block_given?
|
|
96
|
+
log("% #{yield}")
|
|
97
|
+
else
|
|
98
|
+
log("% #{str}")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
95
101
|
end
|
|
96
102
|
|
|
97
103
|
# Write +str+ to +@stderr+
|
data/lib/puma/minissl.rb
CHANGED
data/lib/puma/plugin/systemd.rb
CHANGED
|
@@ -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.
|
|
18
|
-
launcher.events.
|
|
19
|
-
launcher.events.
|
|
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?
|
data/lib/puma/rack/urlmap.rb
CHANGED
|
@@ -70,7 +70,7 @@ module Puma::Rack
|
|
|
70
70
|
return app.call(env)
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
-
[404, {'
|
|
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.
|
|
@@ -67,17 +75,18 @@ module Puma
|
|
|
67
75
|
private
|
|
68
76
|
|
|
69
77
|
def select_loop
|
|
70
|
-
close_selector = true
|
|
71
78
|
begin
|
|
72
79
|
until @input.closed? && @input.empty?
|
|
73
80
|
# Wakeup any registered object that receives incoming data.
|
|
74
81
|
# Block until the earliest timeout or Selector#wakeup is called.
|
|
75
82
|
timeout = (earliest = @timeouts.first) && earliest.timeout
|
|
76
|
-
@selector.select(timeout)
|
|
83
|
+
@selector.select(timeout) do |monitor|
|
|
84
|
+
wakeup!(monitor.value)
|
|
85
|
+
end
|
|
77
86
|
|
|
78
87
|
# Wakeup all objects that timed out.
|
|
79
|
-
timed_out = @timeouts.take_while {|
|
|
80
|
-
timed_out.each { |
|
|
88
|
+
timed_out = @timeouts.take_while { |client| client.timeout == 0 }
|
|
89
|
+
timed_out.each { |client| wakeup!(client) }
|
|
81
90
|
|
|
82
91
|
unless @input.empty?
|
|
83
92
|
until @input.empty?
|
|
@@ -91,23 +100,19 @@ module Puma
|
|
|
91
100
|
STDERR.puts "Error in reactor loop escaped: #{e.message} (#{e.class})"
|
|
92
101
|
STDERR.puts e.backtrace
|
|
93
102
|
|
|
94
|
-
|
|
95
|
-
# is odd. Regardless, it may continue for thousands of calls if retried.
|
|
96
|
-
# Also, when it raises, @selector.close also raises an error.
|
|
97
|
-
if NoMethodError === e
|
|
98
|
-
close_selector = false
|
|
99
|
-
else
|
|
100
|
-
retry
|
|
101
|
-
end
|
|
103
|
+
retry
|
|
102
104
|
end
|
|
105
|
+
|
|
103
106
|
# Wakeup all remaining objects on shutdown.
|
|
104
107
|
@timeouts.each(&@block)
|
|
105
|
-
@selector.close
|
|
108
|
+
@selector.close
|
|
106
109
|
end
|
|
107
110
|
|
|
108
111
|
# Start monitoring the object.
|
|
109
112
|
def register(client)
|
|
110
113
|
@selector.register(client.to_io, :r).value = client
|
|
114
|
+
@reactor_size += 1
|
|
115
|
+
@reactor_max = @reactor_size if @reactor_max < @reactor_size
|
|
111
116
|
@timeouts << client
|
|
112
117
|
rescue ArgumentError
|
|
113
118
|
# unreadable clients raise error when processed by NIO
|
|
@@ -118,6 +123,7 @@ module Puma
|
|
|
118
123
|
def wakeup!(client)
|
|
119
124
|
if @block.call client
|
|
120
125
|
@selector.deregister client.to_io
|
|
126
|
+
@reactor_size -= 1
|
|
121
127
|
@timeouts.delete client
|
|
122
128
|
end
|
|
123
129
|
end
|