puma 3.11.2 → 6.0.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 (99) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +1708 -422
  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/docs/jungle/rc.d/README.md +74 -0
  15. data/docs/jungle/rc.d/puma +61 -0
  16. data/docs/jungle/rc.d/puma.conf +10 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +1 -1
  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 +100 -115
  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 -35
  44. data/lib/puma/client.rb +369 -232
  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 +124 -77
  52. data/lib/puma/detect.rb +33 -2
  53. data/lib/puma/dsl.rb +685 -138
  54. data/lib/puma/error_logger.rb +112 -0
  55. data/lib/puma/events.rb +17 -111
  56. data/lib/puma/io_buffer.rb +34 -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 +197 -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 +256 -70
  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 +607 -0
  72. data/lib/puma/runner.rb +94 -71
  73. data/lib/puma/server.rb +336 -703
  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 +185 -91
  78. data/lib/puma/util.rb +23 -10
  79. data/lib/puma.rb +68 -3
  80. data/lib/rack/handler/puma.rb +17 -14
  81. data/tools/Dockerfile +16 -0
  82. data/tools/trickletest.rb +0 -1
  83. metadata +53 -30
  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 -13
  94. data/tools/jungle/init.d/README.md +0 -59
  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,24 +86,43 @@ 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
70
104
 
71
105
  opts.order!(argv) { |a| opts.terminate a }
106
+ opts.parse!
72
107
 
73
108
  @command = argv.shift
74
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
+
75
119
  unless @config_file == '-'
76
- if @config_file.nil? and File.exist?('config/puma.rb')
77
- @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
78
126
  end
79
127
 
80
128
  if @config_file
@@ -86,19 +134,8 @@ module Puma
86
134
  @pidfile ||= config.options[:pidfile]
87
135
  end
88
136
  end
89
-
90
- # check present of command
91
- unless @command
92
- raise "Available commands: #{COMMANDS.join(", ")}"
93
- end
94
-
95
- unless COMMANDS.include? @command
96
- raise "Invalid command: #{@command}"
97
- end
98
-
99
137
  rescue => e
100
138
  @stdout.puts e.message
101
- @stdout.puts e.backtrace
102
139
  exit 1
103
140
  end
104
141
 
@@ -120,7 +157,7 @@ module Puma
120
157
  @pid = sf.pid
121
158
  elsif @pidfile
122
159
  # get pid from pid_file
123
- @pid = File.open(@pidfile).gets.to_i
160
+ @pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
124
161
  end
125
162
  end
126
163
 
@@ -128,17 +165,27 @@ module Puma
128
165
  uri = URI.parse @control_url
129
166
 
130
167
  # create server object by scheme
131
- @server = case uri.scheme
132
- when "tcp"
133
- TCPSocket.new uri.host, uri.port
134
- when "unix"
135
- UNIXSocket.new "#{uri.host}#{uri.path}"
136
- else
137
- raise "Invalid scheme: #{uri.scheme}"
138
- end
139
-
140
- if @command == "status"
141
- 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'
142
189
  else
143
190
  url = "/#{@command}"
144
191
 
@@ -146,10 +193,10 @@ module Puma
146
193
  url = url + "?token=#{@control_auth_token}"
147
194
  end
148
195
 
149
- @server << "GET #{url} HTTP/1.0\r\n\r\n"
196
+ server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
150
197
 
151
- unless data = @server.read
152
- raise "Server closed connection before responding"
198
+ unless data = server.read
199
+ raise 'Server closed connection before responding'
153
200
  end
154
201
 
155
202
  response = data.split("\r\n")
@@ -158,58 +205,59 @@ module Puma
158
205
  raise "Server sent empty response"
159
206
  end
160
207
 
161
- (@http,@code,@message) = response.first.split(" ",3)
208
+ @http, @code, @message = response.first.split(' ',3)
162
209
 
163
- if @code == "403"
164
- raise "Unauthorized access to server (wrong auth token)"
165
- elsif @code == "404"
210
+ if @code == '403'
211
+ raise 'Unauthorized access to server (wrong auth token)'
212
+ elsif @code == '404'
166
213
  raise "Command error: #{response.last}"
167
- elsif @code != "200"
214
+ elsif @code != '200'
168
215
  raise "Bad response from server: #{@code}"
169
216
  end
170
217
 
171
218
  message "Command #{@command} sent success"
172
- 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
173
228
  end
174
-
175
- @server.close
176
229
  end
177
230
 
178
231
  def send_signal
179
232
  unless @pid
180
- raise "Neither pid nor control url available"
233
+ raise 'Neither pid nor control url available'
181
234
  end
182
235
 
183
236
  begin
237
+ sig = CMD_PATH_SIG_MAP[@command]
184
238
 
185
- case @command
186
- when "restart"
187
- Process.kill "SIGUSR2", @pid
188
-
189
- when "halt"
190
- Process.kill "QUIT", @pid
191
-
192
- when "stop"
193
- Process.kill "SIGTERM", @pid
194
-
195
- when "stats"
196
- puts "Stats not available via pid only"
197
- return
198
-
199
- when "reload-worker-directory"
200
- 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
201
242
  return
202
-
203
- when "phased-restart"
204
- Process.kill "SIGUSR1", @pid
205
-
206
- else
207
- message "Puma is started"
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