puma 3.12.6 → 6.3.0

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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1806 -451
  3. data/LICENSE +23 -20
  4. data/README.md +217 -65
  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 +31 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +9 -0
  14. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/docs/kubernetes.md +66 -0
  17. data/docs/nginx.md +2 -2
  18. data/docs/plugins.md +22 -12
  19. data/docs/rails_dev_mode.md +28 -0
  20. data/docs/restart.md +47 -22
  21. data/docs/signals.md +13 -11
  22. data/docs/stats.md +142 -0
  23. data/docs/systemd.md +94 -120
  24. data/docs/testing_benchmarks_local_files.md +150 -0
  25. data/docs/testing_test_rackup_ci_files.md +36 -0
  26. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  27. data/ext/puma_http11/ext_help.h +1 -1
  28. data/ext/puma_http11/extconf.rb +61 -3
  29. data/ext/puma_http11/http11_parser.c +103 -117
  30. data/ext/puma_http11/http11_parser.h +2 -2
  31. data/ext/puma_http11/http11_parser.java.rl +22 -38
  32. data/ext/puma_http11/http11_parser.rl +3 -3
  33. data/ext/puma_http11/http11_parser_common.rl +6 -6
  34. data/ext/puma_http11/mini_ssl.c +389 -99
  35. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  36. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  37. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
  38. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +248 -92
  39. data/ext/puma_http11/puma_http11.c +49 -57
  40. data/lib/puma/app/status.rb +71 -49
  41. data/lib/puma/binder.rb +244 -150
  42. data/lib/puma/cli.rb +38 -34
  43. data/lib/puma/client.rb +388 -244
  44. data/lib/puma/cluster/worker.rb +180 -0
  45. data/lib/puma/cluster/worker_handle.rb +97 -0
  46. data/lib/puma/cluster.rb +261 -243
  47. data/lib/puma/commonlogger.rb +21 -14
  48. data/lib/puma/configuration.rb +116 -88
  49. data/lib/puma/const.rb +154 -104
  50. data/lib/puma/control_cli.rb +115 -70
  51. data/lib/puma/detect.rb +33 -2
  52. data/lib/puma/dsl.rb +764 -134
  53. data/lib/puma/error_logger.rb +113 -0
  54. data/lib/puma/events.rb +16 -112
  55. data/lib/puma/io_buffer.rb +42 -5
  56. data/lib/puma/jruby_restart.rb +2 -59
  57. data/lib/puma/json_serialization.rb +96 -0
  58. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  59. data/lib/puma/launcher.rb +184 -133
  60. data/lib/puma/log_writer.rb +147 -0
  61. data/lib/puma/minissl/context_builder.rb +93 -0
  62. data/lib/puma/minissl.rb +263 -70
  63. data/lib/puma/null_io.rb +18 -1
  64. data/lib/puma/plugin/systemd.rb +90 -0
  65. data/lib/puma/plugin/tmp_restart.rb +3 -1
  66. data/lib/puma/plugin.rb +7 -13
  67. data/lib/puma/rack/builder.rb +9 -11
  68. data/lib/puma/rack/urlmap.rb +2 -0
  69. data/lib/puma/rack_default.rb +21 -4
  70. data/lib/puma/reactor.rb +93 -315
  71. data/lib/puma/request.rb +671 -0
  72. data/lib/puma/runner.rb +94 -69
  73. data/lib/puma/sd_notify.rb +149 -0
  74. data/lib/puma/server.rb +327 -772
  75. data/lib/puma/single.rb +20 -74
  76. data/lib/puma/state_file.rb +45 -8
  77. data/lib/puma/thread_pool.rb +146 -92
  78. data/lib/puma/util.rb +22 -10
  79. data/lib/puma.rb +60 -5
  80. data/lib/rack/handler/puma.rb +116 -90
  81. data/tools/Dockerfile +16 -0
  82. data/tools/trickletest.rb +0 -1
  83. metadata +54 -32
  84. data/ext/puma_http11/io_buffer.c +0 -155
  85. data/lib/puma/accept_nonblock.rb +0 -23
  86. data/lib/puma/compat.rb +0 -14
  87. data/lib/puma/convenient.rb +0 -25
  88. data/lib/puma/daemon_ext.rb +0 -33
  89. data/lib/puma/delegation.rb +0 -13
  90. data/lib/puma/java_io_buffer.rb +0 -47
  91. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  92. data/lib/puma/tcp_logger.rb +0 -41
  93. data/tools/jungle/README.md +0 -19
  94. data/tools/jungle/init.d/README.md +0 -61
  95. data/tools/jungle/init.d/puma +0 -421
  96. data/tools/jungle/init.d/run-puma +0 -18
  97. data/tools/jungle/upstart/README.md +0 -61
  98. data/tools/jungle/upstart/puma-manager.conf +0 -31
  99. data/tools/jungle/upstart/puma.conf +0 -69
  100. /data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
data/lib/puma/runner.rb CHANGED
@@ -1,23 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/server'
4
- require 'puma/const'
3
+ require_relative 'server'
4
+ require_relative 'const'
5
5
 
6
6
  module Puma
7
7
  # Generic class that is used by `Puma::Cluster` and `Puma::Single` to
8
8
  # serve requests. This class spawns a new instance of `Puma::Server` via
9
9
  # a call to `start_server`.
10
10
  class Runner
11
- def initialize(cli, events)
12
- @launcher = cli
13
- @events = events
14
- @options = cli.options
11
+ def initialize(launcher)
12
+ @launcher = launcher
13
+ @log_writer = launcher.log_writer
14
+ @events = launcher.events
15
+ @config = launcher.config
16
+ @options = launcher.options
15
17
  @app = nil
16
18
  @control = nil
19
+ @started_at = Time.now
20
+ @wakeup = nil
17
21
  end
18
22
 
19
- def daemon?
20
- @options[:daemon]
23
+ # Returns the hash of configuration options.
24
+ # @return [Puma::UserFileDefaultOptions]
25
+ attr_reader :options
26
+
27
+ def wakeup!
28
+ return unless @wakeup
29
+
30
+ @wakeup.write "!" unless @wakeup.closed?
31
+
32
+ rescue SystemCallError, IOError
33
+ Puma::Util.purge_interrupt_queue
21
34
  end
22
35
 
23
36
  def development?
@@ -29,57 +42,51 @@ module Puma
29
42
  end
30
43
 
31
44
  def log(str)
32
- @events.log str
45
+ @log_writer.log str
33
46
  end
34
47
 
35
- def before_restart
36
- @control.stop(true) if @control
48
+ # @version 5.0.0
49
+ def stop_control
50
+ @control&.stop true
37
51
  end
38
52
 
39
53
  def error(str)
40
- @events.error str
54
+ @log_writer.error str
41
55
  end
42
56
 
43
57
  def debug(str)
44
- @events.log "- #{str}" if @options[:debug]
58
+ @log_writer.log "- #{str}" if @options[:debug]
45
59
  end
46
60
 
47
61
  def start_control
48
62
  str = @options[:control_url]
49
63
  return unless str
50
64
 
51
- require 'puma/app/status'
52
-
53
- uri = URI.parse str
54
-
55
- app = Puma::App::Status.new @launcher
65
+ require_relative 'app/status'
56
66
 
57
67
  if token = @options[:control_auth_token]
58
- app.auth_token = token unless token.empty? or token == :none
68
+ token = nil if token.empty? || token == 'none'
59
69
  end
60
70
 
61
- control = Puma::Server.new app, @launcher.events
62
- control.min_threads = 0
63
- control.max_threads = 1
71
+ app = Puma::App::Status.new @launcher, token
64
72
 
65
- case uri.scheme
66
- when "tcp"
67
- log "* Starting control server on #{str}"
68
- control.add_tcp_listener uri.host, uri.port
69
- when "unix"
70
- log "* Starting control server on #{str}"
71
- path = "#{uri.host}#{uri.path}"
72
- mask = @options[:control_url_umask]
73
+ # A Reactor is not created aand nio4r is not loaded when 'queue_requests: false'
74
+ # Use `nil` for events, no hooks in control server
75
+ control = Puma::Server.new app, nil,
76
+ { min_threads: 0, max_threads: 1, queue_requests: false, log_writer: @log_writer }
73
77
 
74
- control.add_unix_listener path, mask
75
- else
76
- error "Invalid control URI: #{str}"
77
- end
78
+ control.binder.parse [str], nil, 'Starting control server'
78
79
 
79
- control.run
80
+ control.run thread_name: 'ctl'
80
81
  @control = control
81
82
  end
82
83
 
84
+ # @version 5.0.0
85
+ def close_control_listeners
86
+ @control.binder.close_listeners if @control
87
+ end
88
+
89
+ # @!attribute [r] ruby_engine
83
90
  def ruby_engine
84
91
  if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
85
92
  "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
@@ -95,14 +102,18 @@ module Puma
95
102
  def output_header(mode)
96
103
  min_t = @options[:min_threads]
97
104
  max_t = @options[:max_threads]
105
+ environment = @options[:environment]
98
106
 
99
107
  log "Puma starting in #{mode} mode..."
100
- log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
101
- log "* Min threads: #{min_t}, max threads: #{max_t}"
102
- log "* Environment: #{ENV['RACK_ENV']}"
108
+ log "* Puma version: #{Puma::Const::PUMA_VERSION} (#{ruby_engine}) (\"#{Puma::Const::CODE_NAME}\")"
109
+ log "* Min threads: #{min_t}"
110
+ log "* Max threads: #{max_t}"
111
+ log "* Environment: #{environment}"
103
112
 
104
- if @options[:mode] == :tcp
105
- log "* Mode: Lopez Express (tcp)"
113
+ if mode == "cluster"
114
+ log "* Master PID: #{Process.pid}"
115
+ else
116
+ log "* PID: #{Process.pid}"
106
117
  end
107
118
  end
108
119
 
@@ -116,69 +127,83 @@ module Puma
116
127
  append = @options[:redirect_append]
117
128
 
118
129
  if stdout
119
- unless Dir.exist?(File.dirname(stdout))
120
- raise "Cannot redirect STDOUT to #{stdout}"
121
- end
130
+ ensure_output_directory_exists(stdout, 'STDOUT')
122
131
 
123
132
  STDOUT.reopen stdout, (append ? "a" : "w")
124
- STDOUT.sync = true
125
133
  STDOUT.puts "=== puma startup: #{Time.now} ==="
134
+ STDOUT.flush unless STDOUT.sync
126
135
  end
127
136
 
128
137
  if stderr
129
- unless Dir.exist?(File.dirname(stderr))
130
- raise "Cannot redirect STDERR to #{stderr}"
131
- end
138
+ ensure_output_directory_exists(stderr, 'STDERR')
132
139
 
133
140
  STDERR.reopen stderr, (append ? "a" : "w")
134
- STDERR.sync = true
135
141
  STDERR.puts "=== puma startup: #{Time.now} ==="
142
+ STDERR.flush unless STDERR.sync
143
+ end
144
+
145
+ if @options[:mutate_stdout_and_stderr_to_sync_on_write]
146
+ STDOUT.sync = true
147
+ STDERR.sync = true
136
148
  end
137
149
  end
138
150
 
139
151
  def load_and_bind
140
- unless @launcher.config.app_configured?
152
+ unless @config.app_configured?
141
153
  error "No application configured, nothing to run"
142
154
  exit 1
143
155
  end
144
156
 
145
- # Load the app before we daemonize.
146
157
  begin
147
- @app = @launcher.config.app
158
+ @app = @config.app
148
159
  rescue Exception => e
149
160
  log "! Unable to load application: #{e.class}: #{e.message}"
150
161
  raise e
151
162
  end
152
163
 
153
- @launcher.binder.parse @options[:binds], self
164
+ @launcher.binder.parse @options[:binds]
154
165
  end
155
166
 
167
+ # @!attribute [r] app
156
168
  def app
157
- @app ||= @launcher.config.app
169
+ @app ||= @config.app
158
170
  end
159
171
 
160
172
  def start_server
161
- min_t = @options[:min_threads]
162
- max_t = @options[:max_threads]
163
-
164
- server = Puma::Server.new app, @launcher.events, @options
165
- server.min_threads = min_t
166
- server.max_threads = max_t
167
- server.inherit_binder @launcher.binder
173
+ server = Puma::Server.new(app, @events, @options)
174
+ server.inherit_binder(@launcher.binder)
175
+ server
176
+ end
168
177
 
169
- if @options[:mode] == :tcp
170
- server.tcp_mode!
178
+ private
179
+ def ensure_output_directory_exists(path, io_name)
180
+ unless Dir.exist?(File.dirname(path))
181
+ raise "Cannot redirect #{io_name} to #{path}"
171
182
  end
183
+ end
172
184
 
173
- if @options[:early_hints]
174
- server.early_hints = true
175
- end
185
+ def utc_iso8601(val)
186
+ "#{val.utc.strftime '%FT%T'}Z"
187
+ end
176
188
 
177
- unless development? || test?
178
- server.leak_stack_on_error = false
179
- end
189
+ def stats
190
+ {
191
+ versions: {
192
+ puma: Puma::Const::PUMA_VERSION,
193
+ ruby: {
194
+ engine: RUBY_ENGINE,
195
+ version: RUBY_VERSION,
196
+ patchlevel: RUBY_PATCHLEVEL
197
+ }
198
+ }
199
+ }
200
+ end
180
201
 
181
- server
202
+ # this method call should always be guarded by `@log_writer.debug?`
203
+ def debug_loaded_extensions(str)
204
+ @log_writer.debug "────────────────────────────────── #{str}"
205
+ re_ext = /\.#{RbConfig::CONFIG['DLEXT']}\z/i
206
+ $LOADED_FEATURES.grep(re_ext).each { |f| @log_writer.debug(" #{f}") }
182
207
  end
183
208
  end
184
209
  end
@@ -0,0 +1,149 @@
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 do |s|
141
+ s.close_on_exec = true
142
+ s.write(state)
143
+ end
144
+ rescue StandardError => e
145
+ raise NotifyError, "#{e.class}: #{e.message}", e.backtrace
146
+ end
147
+ end
148
+ end
149
+ end