puma 4.3.12 → 6.3.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1729 -521
  3. data/LICENSE +23 -20
  4. data/README.md +169 -45
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +63 -26
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +60 -69
  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/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +2 -2
  19. data/docs/plugins.md +15 -15
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +46 -23
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +84 -128
  25. data/docs/testing_benchmarks_local_files.md +150 -0
  26. data/docs/testing_test_rackup_ci_files.md +36 -0
  27. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  28. data/ext/puma_http11/ext_help.h +1 -1
  29. data/ext/puma_http11/extconf.rb +49 -12
  30. data/ext/puma_http11/http11_parser.c +46 -48
  31. data/ext/puma_http11/http11_parser.h +2 -2
  32. data/ext/puma_http11/http11_parser.java.rl +3 -3
  33. data/ext/puma_http11/http11_parser.rl +3 -3
  34. data/ext/puma_http11/http11_parser_common.rl +2 -2
  35. data/ext/puma_http11/mini_ssl.c +278 -93
  36. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11.java +6 -6
  38. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +4 -6
  39. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +241 -96
  40. data/ext/puma_http11/puma_http11.c +46 -57
  41. data/lib/puma/app/status.rb +53 -39
  42. data/lib/puma/binder.rb +237 -121
  43. data/lib/puma/cli.rb +34 -34
  44. data/lib/puma/client.rb +172 -98
  45. data/lib/puma/cluster/worker.rb +180 -0
  46. data/lib/puma/cluster/worker_handle.rb +97 -0
  47. data/lib/puma/cluster.rb +226 -231
  48. data/lib/puma/commonlogger.rb +21 -14
  49. data/lib/puma/configuration.rb +114 -87
  50. data/lib/puma/const.rb +139 -95
  51. data/lib/puma/control_cli.rb +99 -79
  52. data/lib/puma/detect.rb +33 -2
  53. data/lib/puma/dsl.rb +516 -110
  54. data/lib/puma/error_logger.rb +113 -0
  55. data/lib/puma/events.rb +16 -115
  56. data/lib/puma/io_buffer.rb +44 -2
  57. data/lib/puma/jruby_restart.rb +2 -59
  58. data/lib/puma/json_serialization.rb +96 -0
  59. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  60. data/lib/puma/launcher.rb +164 -155
  61. data/lib/puma/log_writer.rb +147 -0
  62. data/lib/puma/minissl/context_builder.rb +36 -19
  63. data/lib/puma/minissl.rb +230 -55
  64. data/lib/puma/null_io.rb +18 -1
  65. data/lib/puma/plugin/systemd.rb +90 -0
  66. data/lib/puma/plugin/tmp_restart.rb +1 -1
  67. data/lib/puma/plugin.rb +3 -12
  68. data/lib/puma/rack/builder.rb +7 -11
  69. data/lib/puma/rack/urlmap.rb +0 -0
  70. data/lib/puma/rack_default.rb +19 -4
  71. data/lib/puma/reactor.rb +93 -368
  72. data/lib/puma/request.rb +671 -0
  73. data/lib/puma/runner.rb +92 -75
  74. data/lib/puma/sd_notify.rb +149 -0
  75. data/lib/puma/server.rb +321 -794
  76. data/lib/puma/single.rb +20 -74
  77. data/lib/puma/state_file.rb +45 -8
  78. data/lib/puma/thread_pool.rb +140 -68
  79. data/lib/puma/util.rb +21 -4
  80. data/lib/puma.rb +54 -7
  81. data/lib/rack/handler/puma.rb +113 -87
  82. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  83. data/tools/trickletest.rb +0 -0
  84. metadata +33 -24
  85. data/docs/tcp_mode.md +0 -96
  86. data/ext/puma_http11/io_buffer.c +0 -155
  87. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  88. data/lib/puma/accept_nonblock.rb +0 -29
  89. data/lib/puma/tcp_logger.rb +0 -41
  90. data/tools/jungle/README.md +0 -19
  91. data/tools/jungle/init.d/README.md +0 -61
  92. data/tools/jungle/init.d/puma +0 -421
  93. data/tools/jungle/init.d/run-puma +0 -18
  94. data/tools/jungle/upstart/README.md +0 -61
  95. data/tools/jungle/upstart/puma-manager.conf +0 -31
  96. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/runner.rb CHANGED
@@ -1,25 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/server'
4
- require 'puma/const'
5
- require 'puma/minissl/context_builder'
3
+ require_relative 'server'
4
+ require_relative 'const'
6
5
 
7
6
  module Puma
8
7
  # Generic class that is used by `Puma::Cluster` and `Puma::Single` to
9
8
  # serve requests. This class spawns a new instance of `Puma::Server` via
10
9
  # a call to `start_server`.
11
10
  class Runner
12
- def initialize(cli, events)
13
- @launcher = cli
14
- @events = events
15
- @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
16
17
  @app = nil
17
18
  @control = nil
18
19
  @started_at = Time.now
20
+ @wakeup = nil
19
21
  end
20
22
 
21
- def daemon?
22
- @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
23
34
  end
24
35
 
25
36
  def development?
@@ -31,28 +42,27 @@ module Puma
31
42
  end
32
43
 
33
44
  def log(str)
34
- @events.log str
45
+ @log_writer.log str
35
46
  end
36
47
 
37
- def before_restart
38
- @control.stop(true) if @control
48
+ # @version 5.0.0
49
+ def stop_control
50
+ @control&.stop true
39
51
  end
40
52
 
41
53
  def error(str)
42
- @events.error str
54
+ @log_writer.error str
43
55
  end
44
56
 
45
57
  def debug(str)
46
- @events.log "- #{str}" if @options[:debug]
58
+ @log_writer.log "- #{str}" if @options[:debug]
47
59
  end
48
60
 
49
61
  def start_control
50
62
  str = @options[:control_url]
51
63
  return unless str
52
64
 
53
- require 'puma/app/status'
54
-
55
- uri = URI.parse str
65
+ require_relative 'app/status'
56
66
 
57
67
  if token = @options[:control_auth_token]
58
68
  token = nil if token.empty? || token == 'none'
@@ -60,34 +70,23 @@ module Puma
60
70
 
61
71
  app = Puma::App::Status.new @launcher, token
62
72
 
63
- control = Puma::Server.new app, @launcher.events
64
- control.min_threads = 0
65
- control.max_threads = 1
66
-
67
- case uri.scheme
68
- when "ssl"
69
- log "* Starting control server on #{str}"
70
- params = Util.parse_query uri.query
71
- ctx = MiniSSL::ContextBuilder.new(params, @events).context
72
-
73
- control.add_ssl_listener uri.host, uri.port, ctx
74
- when "tcp"
75
- log "* Starting control server on #{str}"
76
- control.add_tcp_listener uri.host, uri.port
77
- when "unix"
78
- log "* Starting control server on #{str}"
79
- path = "#{uri.host}#{uri.path}"
80
- mask = @options[:control_url_umask]
81
-
82
- control.add_unix_listener path, mask
83
- else
84
- error "Invalid control URI: #{str}"
85
- end
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 }
86
77
 
87
- control.run
78
+ control.binder.parse [str], nil, 'Starting control server'
79
+
80
+ control.run thread_name: 'ctl'
88
81
  @control = control
89
82
  end
90
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
91
90
  def ruby_engine
92
91
  if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
93
92
  "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
@@ -103,14 +102,18 @@ module Puma
103
102
  def output_header(mode)
104
103
  min_t = @options[:min_threads]
105
104
  max_t = @options[:max_threads]
105
+ environment = @options[:environment]
106
106
 
107
107
  log "Puma starting in #{mode} mode..."
108
- log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
109
- log "* Min threads: #{min_t}, max threads: #{max_t}"
110
- 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}"
111
112
 
112
- if @options[:mode] == :tcp
113
- log "* Mode: Lopez Express (tcp)"
113
+ if mode == "cluster"
114
+ log "* Master PID: #{Process.pid}"
115
+ else
116
+ log "* PID: #{Process.pid}"
114
117
  end
115
118
  end
116
119
 
@@ -124,69 +127,83 @@ module Puma
124
127
  append = @options[:redirect_append]
125
128
 
126
129
  if stdout
127
- unless Dir.exist?(File.dirname(stdout))
128
- raise "Cannot redirect STDOUT to #{stdout}"
129
- end
130
+ ensure_output_directory_exists(stdout, 'STDOUT')
130
131
 
131
132
  STDOUT.reopen stdout, (append ? "a" : "w")
132
- STDOUT.sync = true
133
133
  STDOUT.puts "=== puma startup: #{Time.now} ==="
134
+ STDOUT.flush unless STDOUT.sync
134
135
  end
135
136
 
136
137
  if stderr
137
- unless Dir.exist?(File.dirname(stderr))
138
- raise "Cannot redirect STDERR to #{stderr}"
139
- end
138
+ ensure_output_directory_exists(stderr, 'STDERR')
140
139
 
141
140
  STDERR.reopen stderr, (append ? "a" : "w")
142
- STDERR.sync = true
143
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
144
148
  end
145
149
  end
146
150
 
147
151
  def load_and_bind
148
- unless @launcher.config.app_configured?
152
+ unless @config.app_configured?
149
153
  error "No application configured, nothing to run"
150
154
  exit 1
151
155
  end
152
156
 
153
- # Load the app before we daemonize.
154
157
  begin
155
- @app = @launcher.config.app
158
+ @app = @config.app
156
159
  rescue Exception => e
157
160
  log "! Unable to load application: #{e.class}: #{e.message}"
158
161
  raise e
159
162
  end
160
163
 
161
- @launcher.binder.parse @options[:binds], self
164
+ @launcher.binder.parse @options[:binds]
162
165
  end
163
166
 
167
+ # @!attribute [r] app
164
168
  def app
165
- @app ||= @launcher.config.app
169
+ @app ||= @config.app
166
170
  end
167
171
 
168
172
  def start_server
169
- min_t = @options[:min_threads]
170
- max_t = @options[:max_threads]
171
-
172
- server = Puma::Server.new app, @launcher.events, @options
173
- server.min_threads = min_t
174
- server.max_threads = max_t
175
- server.inherit_binder @launcher.binder
173
+ server = Puma::Server.new(app, @events, @options)
174
+ server.inherit_binder(@launcher.binder)
175
+ server
176
+ end
176
177
 
177
- if @options[:mode] == :tcp
178
- 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}"
179
182
  end
183
+ end
180
184
 
181
- if @options[:early_hints]
182
- server.early_hints = true
183
- end
185
+ def utc_iso8601(val)
186
+ "#{val.utc.strftime '%FT%T'}Z"
187
+ end
184
188
 
185
- unless development? || test?
186
- server.leak_stack_on_error = false
187
- 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
188
201
 
189
- 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}") }
190
207
  end
191
208
  end
192
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