puma 3.11.1 → 6.6.0

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 (98) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +2092 -422
  3. data/LICENSE +23 -20
  4. data/README.md +301 -69
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +59 -21
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +69 -58
  9. data/docs/fork_worker.md +41 -0
  10. data/docs/java_options.md +54 -0
  11. data/docs/jungle/README.md +9 -0
  12. data/docs/jungle/rc.d/README.md +74 -0
  13. data/docs/jungle/rc.d/puma +61 -0
  14. data/docs/jungle/rc.d/puma.conf +10 -0
  15. data/docs/kubernetes.md +78 -0
  16. data/docs/nginx.md +2 -2
  17. data/docs/plugins.md +26 -12
  18. data/docs/rails_dev_mode.md +28 -0
  19. data/docs/restart.md +48 -22
  20. data/docs/signals.md +13 -11
  21. data/docs/stats.md +147 -0
  22. data/docs/systemd.md +108 -117
  23. data/docs/testing_benchmarks_local_files.md +150 -0
  24. data/docs/testing_test_rackup_ci_files.md +36 -0
  25. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  26. data/ext/puma_http11/ext_help.h +1 -1
  27. data/ext/puma_http11/extconf.rb +68 -3
  28. data/ext/puma_http11/http11_parser.c +106 -118
  29. data/ext/puma_http11/http11_parser.h +2 -2
  30. data/ext/puma_http11/http11_parser.java.rl +22 -38
  31. data/ext/puma_http11/http11_parser.rl +6 -4
  32. data/ext/puma_http11/http11_parser_common.rl +6 -6
  33. data/ext/puma_http11/mini_ssl.c +474 -94
  34. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11.java +136 -121
  36. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
  37. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +251 -88
  38. data/ext/puma_http11/puma_http11.c +53 -58
  39. data/lib/puma/app/status.rb +71 -49
  40. data/lib/puma/binder.rb +257 -151
  41. data/lib/puma/cli.rb +61 -38
  42. data/lib/puma/client.rb +464 -224
  43. data/lib/puma/cluster/worker.rb +183 -0
  44. data/lib/puma/cluster/worker_handle.rb +96 -0
  45. data/lib/puma/cluster.rb +343 -239
  46. data/lib/puma/commonlogger.rb +23 -14
  47. data/lib/puma/configuration.rb +144 -96
  48. data/lib/puma/const.rb +194 -115
  49. data/lib/puma/control_cli.rb +135 -81
  50. data/lib/puma/detect.rb +34 -2
  51. data/lib/puma/dsl.rb +1092 -153
  52. data/lib/puma/error_logger.rb +113 -0
  53. data/lib/puma/events.rb +17 -111
  54. data/lib/puma/io_buffer.rb +44 -5
  55. data/lib/puma/jruby_restart.rb +2 -73
  56. data/lib/puma/json_serialization.rb +96 -0
  57. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  58. data/lib/puma/launcher.rb +205 -138
  59. data/lib/puma/log_writer.rb +147 -0
  60. data/lib/puma/minissl/context_builder.rb +96 -0
  61. data/lib/puma/minissl.rb +279 -70
  62. data/lib/puma/null_io.rb +61 -2
  63. data/lib/puma/plugin/systemd.rb +90 -0
  64. data/lib/puma/plugin/tmp_restart.rb +3 -1
  65. data/lib/puma/plugin.rb +9 -13
  66. data/lib/puma/rack/builder.rb +10 -11
  67. data/lib/puma/rack/urlmap.rb +3 -1
  68. data/lib/puma/rack_default.rb +21 -4
  69. data/lib/puma/reactor.rb +97 -185
  70. data/lib/puma/request.rb +688 -0
  71. data/lib/puma/runner.rb +114 -69
  72. data/lib/puma/sd_notify.rb +146 -0
  73. data/lib/puma/server.rb +409 -704
  74. data/lib/puma/single.rb +29 -72
  75. data/lib/puma/state_file.rb +48 -9
  76. data/lib/puma/thread_pool.rb +234 -93
  77. data/lib/puma/util.rb +23 -10
  78. data/lib/puma.rb +68 -5
  79. data/lib/rack/handler/puma.rb +119 -86
  80. data/tools/Dockerfile +16 -0
  81. data/tools/trickletest.rb +0 -1
  82. metadata +55 -33
  83. data/ext/puma_http11/io_buffer.c +0 -155
  84. data/lib/puma/accept_nonblock.rb +0 -23
  85. data/lib/puma/compat.rb +0 -14
  86. data/lib/puma/convenient.rb +0 -23
  87. data/lib/puma/daemon_ext.rb +0 -31
  88. data/lib/puma/delegation.rb +0 -11
  89. data/lib/puma/java_io_buffer.rb +0 -45
  90. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  91. data/lib/puma/tcp_logger.rb +0 -39
  92. data/tools/jungle/README.md +0 -13
  93. data/tools/jungle/init.d/README.md +0 -59
  94. data/tools/jungle/init.d/puma +0 -421
  95. data/tools/jungle/init.d/run-puma +0 -18
  96. data/tools/jungle/upstart/README.md +0 -61
  97. data/tools/jungle/upstart/puma-manager.conf +0 -31
  98. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/runner.rb CHANGED
@@ -1,77 +1,103 @@
1
- require 'puma/server'
2
- require 'puma/const'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'server'
4
+ require_relative 'const'
3
5
 
4
6
  module Puma
7
+ # Generic class that is used by `Puma::Cluster` and `Puma::Single` to
8
+ # serve requests. This class spawns a new instance of `Puma::Server` via
9
+ # a call to `start_server`.
5
10
  class Runner
6
- def initialize(cli, events)
7
- @launcher = cli
8
- @events = events
9
- @options = cli.options
11
+
12
+ include ::Puma::Const::PipeRequest
13
+
14
+ def initialize(launcher)
15
+ @launcher = launcher
16
+ @log_writer = launcher.log_writer
17
+ @events = launcher.events
18
+ @config = launcher.config
19
+ @options = launcher.options
10
20
  @app = nil
11
21
  @control = nil
22
+ @started_at = Time.now
23
+ @wakeup = nil
12
24
  end
13
25
 
14
- def daemon?
15
- @options[:daemon]
26
+ # Returns the hash of configuration options.
27
+ # @return [Puma::UserFileDefaultOptions]
28
+ attr_reader :options
29
+
30
+ def wakeup!
31
+ return unless @wakeup
32
+
33
+ @wakeup.write PIPE_WAKEUP unless @wakeup.closed?
34
+
35
+ rescue SystemCallError, IOError
36
+ Puma::Util.purge_interrupt_queue
16
37
  end
17
38
 
18
39
  def development?
19
40
  @options[:environment] == "development"
20
41
  end
21
42
 
43
+ def test?
44
+ @options[:environment] == "test"
45
+ end
46
+
22
47
  def log(str)
23
- @events.log str
48
+ @log_writer.log str
24
49
  end
25
50
 
26
- def before_restart
27
- @control.stop(true) if @control
51
+ # @version 5.0.0
52
+ def stop_control
53
+ @control&.stop true
28
54
  end
29
55
 
30
56
  def error(str)
31
- @events.error str
57
+ @log_writer.error str
32
58
  end
33
59
 
34
60
  def debug(str)
35
- @events.log "- #{str}" if @options[:debug]
61
+ @log_writer.log "- #{str}" if @options[:debug]
36
62
  end
37
63
 
38
64
  def start_control
39
65
  str = @options[:control_url]
40
66
  return unless str
41
67
 
42
- require 'puma/app/status'
43
-
44
- uri = URI.parse str
45
-
46
- app = Puma::App::Status.new @launcher
68
+ require_relative 'app/status'
47
69
 
48
70
  if token = @options[:control_auth_token]
49
- app.auth_token = token unless token.empty? or token == :none
71
+ token = nil if token.empty? || token == 'none'
50
72
  end
51
73
 
52
- control = Puma::Server.new app, @launcher.events
53
- control.min_threads = 0
54
- control.max_threads = 1
74
+ app = Puma::App::Status.new @launcher, token
55
75
 
56
- case uri.scheme
57
- when "tcp"
58
- log "* Starting control server on #{str}"
59
- control.add_tcp_listener uri.host, uri.port
60
- when "unix"
61
- log "* Starting control server on #{str}"
62
- path = "#{uri.host}#{uri.path}"
63
- mask = @options[:control_url_umask]
76
+ # A Reactor is not created and nio4r is not loaded when 'queue_requests: false'
77
+ # Use `nil` for events, no hooks in control server
78
+ control = Puma::Server.new app, nil,
79
+ { min_threads: 0, max_threads: 1, queue_requests: false, log_writer: @log_writer }
64
80
 
65
- control.add_unix_listener path, mask
66
- else
67
- error "Invalid control URI: #{str}"
81
+ begin
82
+ control.binder.parse [str], nil, 'Starting control server'
83
+ rescue Errno::EADDRINUSE, Errno::EACCES => e
84
+ raise e, "Error: Control server address '#{str}' is already in use. Original error: #{e.message}"
68
85
  end
69
86
 
70
- control.run
87
+ control.run thread_name: 'ctl'
71
88
  @control = control
72
89
  end
73
90
 
91
+ # @version 5.0.0
92
+ def close_control_listeners
93
+ @control.binder.close_listeners if @control
94
+ end
95
+
96
+ # @!attribute [r] ruby_engine
97
+ # @deprecated Use `RUBY_DESCRIPTION` instead
74
98
  def ruby_engine
99
+ warn "Puma::Runner#ruby_engine is deprecated; use RUBY_DESCRIPTION instead. It will be removed in puma v7."
100
+
75
101
  if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
76
102
  "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
77
103
  else
@@ -86,14 +112,19 @@ module Puma
86
112
  def output_header(mode)
87
113
  min_t = @options[:min_threads]
88
114
  max_t = @options[:max_threads]
115
+ environment = @options[:environment]
89
116
 
90
117
  log "Puma starting in #{mode} mode..."
91
- log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
92
- log "* Min threads: #{min_t}, max threads: #{max_t}"
93
- log "* Environment: #{ENV['RACK_ENV']}"
94
-
95
- if @options[:mode] == :tcp
96
- log "* Mode: Lopez Express (tcp)"
118
+ log "* Puma version: #{Puma::Const::PUMA_VERSION} (\"#{Puma::Const::CODE_NAME}\")"
119
+ log "* Ruby version: #{RUBY_DESCRIPTION}"
120
+ log "* Min threads: #{min_t}"
121
+ log "* Max threads: #{max_t}"
122
+ log "* Environment: #{environment}"
123
+
124
+ if mode == "cluster"
125
+ log "* Master PID: #{Process.pid}"
126
+ else
127
+ log "* PID: #{Process.pid}"
97
128
  end
98
129
  end
99
130
 
@@ -107,69 +138,83 @@ module Puma
107
138
  append = @options[:redirect_append]
108
139
 
109
140
  if stdout
110
- unless Dir.exist?(File.dirname(stdout))
111
- raise "Cannot redirect STDOUT to #{stdout}"
112
- end
141
+ ensure_output_directory_exists(stdout, 'STDOUT')
113
142
 
114
143
  STDOUT.reopen stdout, (append ? "a" : "w")
115
- STDOUT.sync = true
116
144
  STDOUT.puts "=== puma startup: #{Time.now} ==="
145
+ STDOUT.flush unless STDOUT.sync
117
146
  end
118
147
 
119
148
  if stderr
120
- unless Dir.exist?(File.dirname(stderr))
121
- raise "Cannot redirect STDERR to #{stderr}"
122
- end
149
+ ensure_output_directory_exists(stderr, 'STDERR')
123
150
 
124
151
  STDERR.reopen stderr, (append ? "a" : "w")
125
- STDERR.sync = true
126
152
  STDERR.puts "=== puma startup: #{Time.now} ==="
153
+ STDERR.flush unless STDERR.sync
154
+ end
155
+
156
+ if @options[:mutate_stdout_and_stderr_to_sync_on_write]
157
+ STDOUT.sync = true
158
+ STDERR.sync = true
127
159
  end
128
160
  end
129
161
 
130
162
  def load_and_bind
131
- unless @launcher.config.app_configured?
163
+ unless @config.app_configured?
132
164
  error "No application configured, nothing to run"
133
165
  exit 1
134
166
  end
135
167
 
136
- # Load the app before we daemonize.
137
168
  begin
138
- @app = @launcher.config.app
169
+ @app = @config.app
139
170
  rescue Exception => e
140
171
  log "! Unable to load application: #{e.class}: #{e.message}"
141
172
  raise e
142
173
  end
143
174
 
144
- @launcher.binder.parse @options[:binds], self
175
+ @launcher.binder.parse @options[:binds]
145
176
  end
146
177
 
178
+ # @!attribute [r] app
147
179
  def app
148
- @app ||= @launcher.config.app
180
+ @app ||= @config.app
149
181
  end
150
182
 
151
183
  def start_server
152
- min_t = @options[:min_threads]
153
- max_t = @options[:max_threads]
154
-
155
- server = Puma::Server.new app, @launcher.events, @options
156
- server.min_threads = min_t
157
- server.max_threads = max_t
158
- server.inherit_binder @launcher.binder
184
+ server = Puma::Server.new(app, @events, @options)
185
+ server.inherit_binder(@launcher.binder)
186
+ server
187
+ end
159
188
 
160
- if @options[:mode] == :tcp
161
- server.tcp_mode!
189
+ private
190
+ def ensure_output_directory_exists(path, io_name)
191
+ unless Dir.exist?(File.dirname(path))
192
+ raise "Cannot redirect #{io_name} to #{path}"
162
193
  end
194
+ end
163
195
 
164
- if @options[:early_hints]
165
- server.early_hints = true
166
- end
196
+ def utc_iso8601(val)
197
+ "#{val.utc.strftime '%FT%T'}Z"
198
+ end
167
199
 
168
- unless development?
169
- server.leak_stack_on_error = false
170
- end
200
+ def stats
201
+ {
202
+ versions: {
203
+ puma: Puma::Const::PUMA_VERSION,
204
+ ruby: {
205
+ engine: RUBY_ENGINE,
206
+ version: RUBY_VERSION,
207
+ patchlevel: RUBY_PATCHLEVEL
208
+ }
209
+ }
210
+ }
211
+ end
171
212
 
172
- server
213
+ # this method call should always be guarded by `@log_writer.debug?`
214
+ def debug_loaded_extensions(str)
215
+ @log_writer.debug "────────────────────────────────── #{str}"
216
+ re_ext = /\.#{RbConfig::CONFIG['DLEXT']}\z/i
217
+ $LOADED_FEATURES.grep(re_ext).each { |f| @log_writer.debug(" #{f}") }
173
218
  end
174
219
  end
175
220
  end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+
5
+ module Puma
6
+ # The MIT License
7
+ #
8
+ # Copyright (c) 2017-2022 Agis Anastasopoulos
9
+ #
10
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
11
+ # this software and associated documentation files (the "Software"), to deal in
12
+ # the Software without restriction, including without limitation the rights to
13
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
14
+ # the Software, and to permit persons to whom the Software is furnished to do so,
15
+ # subject to the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be included in all
18
+ # copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
22
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
23
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
24
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+ #
27
+ # This is a copy of https://github.com/agis/ruby-sdnotify as of commit cca575c
28
+ # The only changes made was "rehoming" it within the Puma module to avoid
29
+ # namespace collisions and applying standard's code formatting style.
30
+ #
31
+ # SdNotify is a pure-Ruby implementation of sd_notify(3). It can be used to
32
+ # notify systemd about state changes. Methods of this package are no-op on
33
+ # non-systemd systems (eg. Darwin).
34
+ #
35
+ # The API maps closely to the original implementation of sd_notify(3),
36
+ # therefore be sure to check the official man pages prior to using SdNotify.
37
+ #
38
+ # @see https://www.freedesktop.org/software/systemd/man/sd_notify.html
39
+ module SdNotify
40
+ # Exception raised when there's an error writing to the notification socket
41
+ class NotifyError < RuntimeError; end
42
+
43
+ READY = "READY=1"
44
+ RELOADING = "RELOADING=1"
45
+ STOPPING = "STOPPING=1"
46
+ STATUS = "STATUS="
47
+ ERRNO = "ERRNO="
48
+ MAINPID = "MAINPID="
49
+ WATCHDOG = "WATCHDOG=1"
50
+ FDSTORE = "FDSTORE=1"
51
+
52
+ def self.ready(unset_env=false)
53
+ notify(READY, unset_env)
54
+ end
55
+
56
+ def self.reloading(unset_env=false)
57
+ notify(RELOADING, unset_env)
58
+ end
59
+
60
+ def self.stopping(unset_env=false)
61
+ notify(STOPPING, unset_env)
62
+ end
63
+
64
+ # @param status [String] a custom status string that describes the current
65
+ # state of the service
66
+ def self.status(status, unset_env=false)
67
+ notify("#{STATUS}#{status}", unset_env)
68
+ end
69
+
70
+ # @param errno [Integer]
71
+ def self.errno(errno, unset_env=false)
72
+ notify("#{ERRNO}#{errno}", unset_env)
73
+ end
74
+
75
+ # @param pid [Integer]
76
+ def self.mainpid(pid, unset_env=false)
77
+ notify("#{MAINPID}#{pid}", unset_env)
78
+ end
79
+
80
+ def self.watchdog(unset_env=false)
81
+ notify(WATCHDOG, unset_env)
82
+ end
83
+
84
+ def self.fdstore(unset_env=false)
85
+ notify(FDSTORE, unset_env)
86
+ end
87
+
88
+ # @param [Boolean] true if the service manager expects watchdog keep-alive
89
+ # notification messages to be sent from this process.
90
+ #
91
+ # If the $WATCHDOG_USEC environment variable is set,
92
+ # and the $WATCHDOG_PID variable is unset or set to the PID of the current
93
+ # process
94
+ #
95
+ # @note Unlike sd_watchdog_enabled(3), this method does not mutate the
96
+ # environment.
97
+ def self.watchdog?
98
+ wd_usec = ENV["WATCHDOG_USEC"]
99
+ wd_pid = ENV["WATCHDOG_PID"]
100
+
101
+ return false if !wd_usec
102
+
103
+ begin
104
+ wd_usec = Integer(wd_usec)
105
+ rescue
106
+ return false
107
+ end
108
+
109
+ return false if wd_usec <= 0
110
+ return true if !wd_pid || wd_pid == $$.to_s
111
+
112
+ false
113
+ end
114
+
115
+ # Notify systemd with the provided state, via the notification socket, if
116
+ # any.
117
+ #
118
+ # Generally this method will be used indirectly through the other methods
119
+ # of the library.
120
+ #
121
+ # @param state [String]
122
+ # @param unset_env [Boolean]
123
+ #
124
+ # @return [Fixnum, nil] the number of bytes written to the notification
125
+ # socket or nil if there was no socket to report to (eg. the program wasn't
126
+ # started by systemd)
127
+ #
128
+ # @raise [NotifyError] if there was an error communicating with the systemd
129
+ # socket
130
+ #
131
+ # @see https://www.freedesktop.org/software/systemd/man/sd_notify.html
132
+ def self.notify(state, unset_env=false)
133
+ sock = ENV["NOTIFY_SOCKET"]
134
+
135
+ return nil if !sock
136
+
137
+ ENV.delete("NOTIFY_SOCKET") if unset_env
138
+
139
+ begin
140
+ Addrinfo.unix(sock, :DGRAM).connect { |s| s.write state }
141
+ rescue StandardError => e
142
+ raise NotifyError, "#{e.class}: #{e.message}", e.backtrace
143
+ end
144
+ end
145
+ end
146
+ end