puma 3.11.4 → 6.0.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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1717 -432
  3. data/LICENSE +23 -20
  4. data/README.md +190 -64
  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/{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 +22 -12
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +47 -22
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +95 -120
  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 -2
  28. data/ext/puma_http11/ext_help.h +1 -1
  29. data/ext/puma_http11/extconf.rb +61 -3
  30. data/ext/puma_http11/http11_parser.c +106 -118
  31. data/ext/puma_http11/http11_parser.h +2 -2
  32. data/ext/puma_http11/http11_parser.java.rl +22 -38
  33. data/ext/puma_http11/http11_parser.rl +6 -4
  34. data/ext/puma_http11/http11_parser_common.rl +6 -6
  35. data/ext/puma_http11/mini_ssl.c +376 -93
  36. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  38. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
  39. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +250 -88
  40. data/ext/puma_http11/puma_http11.c +49 -57
  41. data/lib/puma/app/status.rb +71 -49
  42. data/lib/puma/binder.rb +243 -148
  43. data/lib/puma/cli.rb +50 -36
  44. data/lib/puma/client.rb +373 -233
  45. data/lib/puma/cluster/worker.rb +175 -0
  46. data/lib/puma/cluster/worker_handle.rb +97 -0
  47. data/lib/puma/cluster.rb +268 -235
  48. data/lib/puma/commonlogger.rb +4 -2
  49. data/lib/puma/configuration.rb +116 -88
  50. data/lib/puma/const.rb +49 -30
  51. data/lib/puma/control_cli.rb +123 -76
  52. data/lib/puma/detect.rb +33 -2
  53. data/lib/puma/dsl.rb +685 -135
  54. data/lib/puma/error_logger.rb +112 -0
  55. data/lib/puma/events.rb +17 -111
  56. data/lib/puma/io_buffer.rb +44 -5
  57. data/lib/puma/jruby_restart.rb +4 -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 +196 -130
  61. data/lib/puma/log_writer.rb +137 -0
  62. data/lib/puma/minissl/context_builder.rb +92 -0
  63. data/lib/puma/minissl.rb +249 -69
  64. data/lib/puma/null_io.rb +20 -1
  65. data/lib/puma/plugin/tmp_restart.rb +3 -1
  66. data/lib/puma/plugin.rb +9 -13
  67. data/lib/puma/rack/builder.rb +8 -9
  68. data/lib/puma/rack/urlmap.rb +2 -0
  69. data/lib/puma/rack_default.rb +3 -1
  70. data/lib/puma/reactor.rb +90 -187
  71. data/lib/puma/request.rb +644 -0
  72. data/lib/puma/runner.rb +94 -71
  73. data/lib/puma/server.rb +337 -715
  74. data/lib/puma/single.rb +27 -72
  75. data/lib/puma/state_file.rb +46 -7
  76. data/lib/puma/systemd.rb +47 -0
  77. data/lib/puma/thread_pool.rb +184 -93
  78. data/lib/puma/util.rb +23 -10
  79. data/lib/puma.rb +60 -3
  80. data/lib/rack/handler/puma.rb +17 -15
  81. data/tools/Dockerfile +16 -0
  82. data/tools/trickletest.rb +0 -1
  83. metadata +53 -33
  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 -23
  88. data/lib/puma/daemon_ext.rb +0 -31
  89. data/lib/puma/delegation.rb +0 -11
  90. data/lib/puma/java_io_buffer.rb +0 -45
  91. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  92. data/lib/puma/tcp_logger.rb +0 -39
  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
@@ -1,15 +1,43 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'optparse'
2
- require 'puma/state_file'
3
- require 'puma/const'
4
- require 'puma/detect'
5
- require 'puma/configuration'
4
+ require_relative 'state_file'
5
+ require_relative 'const'
6
+ require_relative 'detect'
7
+ require_relative 'configuration'
6
8
  require 'uri'
7
9
  require 'socket'
8
10
 
9
11
  module Puma
10
12
  class ControlCLI
11
13
 
12
- COMMANDS = %w{halt restart phased-restart start stats status stop reload-worker-directory gc gc-stats}
14
+ # values must be string or nil
15
+ # value of `nil` means command cannot be processed via signal
16
+ # @version 5.0.3
17
+ CMD_PATH_SIG_MAP = {
18
+ 'gc' => nil,
19
+ 'gc-stats' => nil,
20
+ 'halt' => 'SIGQUIT',
21
+ 'info' => 'SIGINFO',
22
+ 'phased-restart' => 'SIGUSR1',
23
+ 'refork' => 'SIGURG',
24
+ 'reload-worker-directory' => nil,
25
+ 'reopen-log' => 'SIGHUP',
26
+ 'restart' => 'SIGUSR2',
27
+ 'start' => nil,
28
+ 'stats' => nil,
29
+ 'status' => '',
30
+ 'stop' => 'SIGTERM',
31
+ 'thread-backtraces' => nil,
32
+ 'worker-count-down' => 'SIGTTOU',
33
+ 'worker-count-up' => 'SIGTTIN'
34
+ }.freeze
35
+
36
+ # commands that cannot be used in a request
37
+ NO_REQ_COMMANDS = %w[info reopen-log worker-count-down worker-count-up].freeze
38
+
39
+ # @version 5.0.0
40
+ PRINTABLE_COMMANDS = %w[gc-stats stats thread-backtraces].freeze
13
41
 
14
42
  def initialize(argv, stdout=STDOUT, stderr=STDERR)
15
43
  @state = nil
@@ -20,6 +48,7 @@ module Puma
20
48
  @control_auth_token = nil
21
49
  @config_file = nil
22
50
  @command = nil
51
+ @environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
23
52
 
24
53
  @argv = argv.dup
25
54
  @stdout = stdout
@@ -27,7 +56,7 @@ module Puma
27
56
  @cli_options = {}
28
57
 
29
58
  opts = OptionParser.new do |o|
30
- o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{COMMANDS.join("|")})"
59
+ o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{CMD_PATH_SIG_MAP.keys.join("|")})"
31
60
 
32
61
  o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
33
62
  @state = arg
@@ -57,13 +86,18 @@ module Puma
57
86
  @config_file = arg
58
87
  end
59
88
 
89
+ o.on "-e", "--environment ENVIRONMENT",
90
+ "The environment to run the Rack app on (default development)" do |arg|
91
+ @environment = arg
92
+ end
93
+
60
94
  o.on_tail("-H", "--help", "Show this message") do
61
95
  @stdout.puts o
62
96
  exit
63
97
  end
64
98
 
65
99
  o.on_tail("-V", "--version", "Show version") do
66
- puts Const::PUMA_VERSION
100
+ @stdout.puts Const::PUMA_VERSION
67
101
  exit
68
102
  end
69
103
  end
@@ -73,9 +107,22 @@ module Puma
73
107
 
74
108
  @command = argv.shift
75
109
 
110
+ # check presence of command
111
+ unless @command
112
+ raise "Available commands: #{CMD_PATH_SIG_MAP.keys.join(", ")}"
113
+ end
114
+
115
+ unless CMD_PATH_SIG_MAP.key? @command
116
+ raise "Invalid command: #{@command}"
117
+ end
118
+
76
119
  unless @config_file == '-'
77
- if @config_file.nil? and File.exist?('config/puma.rb')
78
- @config_file = 'config/puma.rb'
120
+ environment = @environment || 'development'
121
+
122
+ if @config_file.nil?
123
+ @config_file = %W(config/puma/#{environment}.rb config/puma.rb).find do |f|
124
+ File.exist?(f)
125
+ end
79
126
  end
80
127
 
81
128
  if @config_file
@@ -87,19 +134,8 @@ module Puma
87
134
  @pidfile ||= config.options[:pidfile]
88
135
  end
89
136
  end
90
-
91
- # check present of command
92
- unless @command
93
- raise "Available commands: #{COMMANDS.join(", ")}"
94
- end
95
-
96
- unless COMMANDS.include? @command
97
- raise "Invalid command: #{@command}"
98
- end
99
-
100
137
  rescue => e
101
138
  @stdout.puts e.message
102
- @stdout.puts e.backtrace
103
139
  exit 1
104
140
  end
105
141
 
@@ -121,7 +157,7 @@ module Puma
121
157
  @pid = sf.pid
122
158
  elsif @pidfile
123
159
  # get pid from pid_file
124
- @pid = File.open(@pidfile).gets.to_i
160
+ @pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
125
161
  end
126
162
  end
127
163
 
@@ -129,17 +165,27 @@ module Puma
129
165
  uri = URI.parse @control_url
130
166
 
131
167
  # create server object by scheme
132
- @server = case uri.scheme
133
- when "tcp"
134
- TCPSocket.new uri.host, uri.port
135
- when "unix"
136
- UNIXSocket.new "#{uri.host}#{uri.path}"
137
- else
138
- raise "Invalid scheme: #{uri.scheme}"
139
- end
140
-
141
- if @command == "status"
142
- message "Puma is started"
168
+ server =
169
+ case uri.scheme
170
+ when 'ssl'
171
+ require 'openssl'
172
+ OpenSSL::SSL::SSLSocket.new(
173
+ TCPSocket.new(uri.host, uri.port),
174
+ OpenSSL::SSL::SSLContext.new)
175
+ .tap { |ssl| ssl.sync_close = true } # default is false
176
+ .tap(&:connect)
177
+ when 'tcp'
178
+ TCPSocket.new uri.host, uri.port
179
+ when 'unix'
180
+ # check for abstract UNIXSocket
181
+ UNIXSocket.new(@control_url.start_with?('unix://@') ?
182
+ "\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
183
+ else
184
+ raise "Invalid scheme: #{uri.scheme}"
185
+ end
186
+
187
+ if @command == 'status'
188
+ message 'Puma is started'
143
189
  else
144
190
  url = "/#{@command}"
145
191
 
@@ -147,10 +193,10 @@ module Puma
147
193
  url = url + "?token=#{@control_auth_token}"
148
194
  end
149
195
 
150
- @server << "GET #{url} HTTP/1.0\r\n\r\n"
196
+ server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
151
197
 
152
- unless data = @server.read
153
- raise "Server closed connection before responding"
198
+ unless data = server.read
199
+ raise 'Server closed connection before responding'
154
200
  end
155
201
 
156
202
  response = data.split("\r\n")
@@ -159,57 +205,59 @@ module Puma
159
205
  raise "Server sent empty response"
160
206
  end
161
207
 
162
- (@http,@code,@message) = response.first.split(" ",3)
208
+ @http, @code, @message = response.first.split(' ',3)
163
209
 
164
- if @code == "403"
165
- raise "Unauthorized access to server (wrong auth token)"
166
- elsif @code == "404"
210
+ if @code == '403'
211
+ raise 'Unauthorized access to server (wrong auth token)'
212
+ elsif @code == '404'
167
213
  raise "Command error: #{response.last}"
168
- elsif @code != "200"
214
+ elsif @code != '200'
169
215
  raise "Bad response from server: #{@code}"
170
216
  end
171
217
 
172
218
  message "Command #{@command} sent success"
173
- message response.last if @command == "stats" || @command == "gc-stats"
219
+ message response.last if PRINTABLE_COMMANDS.include?(@command)
220
+ end
221
+ ensure
222
+ if server
223
+ if uri.scheme == 'ssl'
224
+ server.sysclose
225
+ else
226
+ server.close unless server.closed?
227
+ end
174
228
  end
175
-
176
- @server.close
177
229
  end
178
230
 
179
231
  def send_signal
180
232
  unless @pid
181
- raise "Neither pid nor control url available"
233
+ raise 'Neither pid nor control url available'
182
234
  end
183
235
 
184
236
  begin
237
+ sig = CMD_PATH_SIG_MAP[@command]
185
238
 
186
- case @command
187
- when "restart"
188
- Process.kill "SIGUSR2", @pid
189
-
190
- when "halt"
191
- Process.kill "QUIT", @pid
192
-
193
- when "stop"
194
- Process.kill "SIGTERM", @pid
195
-
196
- when "stats"
197
- puts "Stats not available via pid only"
198
- return
199
-
200
- when "reload-worker-directory"
201
- puts "reload-worker-directory not available via pid only"
239
+ if sig.nil?
240
+ @stdout.puts "'#{@command}' not available via pid only"
241
+ @stdout.flush unless @stdout.sync
202
242
  return
203
-
204
- when "phased-restart"
205
- Process.kill "SIGUSR1", @pid
206
-
207
- else
243
+ elsif sig.start_with? 'SIG'
244
+ if Signal.list.key? sig.sub(/\ASIG/, '')
245
+ Process.kill sig, @pid
246
+ else
247
+ raise "Signal '#{sig}' not available'"
248
+ end
249
+ elsif @command == 'status'
250
+ begin
251
+ Process.kill 0, @pid
252
+ @stdout.puts 'Puma is started'
253
+ @stdout.flush unless @stdout.sync
254
+ rescue Errno::ESRCH
255
+ raise 'Puma is not running'
256
+ end
208
257
  return
209
258
  end
210
-
211
259
  rescue SystemCallError
212
- if @command == "restart"
260
+ if @command == 'restart'
213
261
  start
214
262
  else
215
263
  raise "No pid '#{@pid}' found"
@@ -220,25 +268,23 @@ module Puma
220
268
  end
221
269
 
222
270
  def run
223
- start if @command == "start"
224
-
271
+ return start if @command == 'start'
225
272
  prepare_configuration
226
273
 
227
- if Puma.windows?
274
+ if Puma.windows? || @control_url && !NO_REQ_COMMANDS.include?(@command)
228
275
  send_request
229
276
  else
230
- @control_url ? send_request : send_signal
277
+ send_signal
231
278
  end
232
279
 
233
280
  rescue => e
234
281
  message e.message
235
- message e.backtrace
236
282
  exit 1
237
283
  end
238
284
 
239
- private
285
+ private
240
286
  def start
241
- require 'puma/cli'
287
+ require_relative 'cli'
242
288
 
243
289
  run_args = []
244
290
 
@@ -248,14 +294,15 @@ module Puma
248
294
  run_args += ["--control-url", @control_url] if @control_url
249
295
  run_args += ["--control-token", @control_auth_token] if @control_auth_token
250
296
  run_args += ["-C", @config_file] if @config_file
297
+ run_args += ["-e", @environment] if @environment
251
298
 
252
- events = Puma::Events.new @stdout, @stderr
299
+ log_writer = Puma::LogWriter.new(@stdout, @stderr)
253
300
 
254
301
  # replace $0 because puma use it to generate restart command
255
302
  puma_cmd = $0.gsub(/pumactl$/, 'puma')
256
303
  $0 = puma_cmd if File.exist?(puma_cmd)
257
304
 
258
- cli = Puma::CLI.new run_args, events
305
+ cli = Puma::CLI.new run_args, log_writer
259
306
  cli.run
260
307
  end
261
308
  end
data/lib/puma/detect.rb CHANGED
@@ -1,13 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file can be loaded independently of puma.rb, so it cannot have any code
4
+ # that assumes puma.rb is loaded.
5
+
6
+
1
7
  module Puma
2
- IS_JRUBY = defined?(JRUBY_VERSION)
8
+ # @version 5.2.1
9
+ HAS_FORK = ::Process.respond_to? :fork
10
+
11
+ HAS_NATIVE_IO_WAIT = ::IO.public_instance_methods(false).include? :wait_readable
12
+
13
+ IS_JRUBY = Object.const_defined? :JRUBY_VERSION
14
+
15
+ IS_OSX = RUBY_PLATFORM.include? 'darwin'
16
+
17
+ IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/) ||
18
+ IS_JRUBY && RUBY_DESCRIPTION.include?('mswin')
19
+
20
+ # @version 5.2.0
21
+ IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
3
22
 
4
23
  def self.jruby?
5
24
  IS_JRUBY
6
25
  end
7
26
 
8
- IS_WINDOWS = RUBY_PLATFORM =~ /mswin|ming|cygwin/
27
+ def self.osx?
28
+ IS_OSX
29
+ end
9
30
 
10
31
  def self.windows?
11
32
  IS_WINDOWS
12
33
  end
34
+
35
+ # @version 5.0.0
36
+ def self.mri?
37
+ IS_MRI
38
+ end
39
+
40
+ # @version 5.0.0
41
+ def self.forkable?
42
+ HAS_FORK
43
+ end
13
44
  end