puma 4.3.6-java → 5.0.2-java

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1153 -518
  3. data/LICENSE +23 -20
  4. data/README.md +26 -13
  5. data/docs/architecture.md +3 -3
  6. data/docs/deployment.md +9 -3
  7. data/docs/fork_worker.md +31 -0
  8. data/docs/jungle/README.md +13 -0
  9. data/{tools → docs}/jungle/rc.d/README.md +0 -0
  10. data/{tools → docs}/jungle/rc.d/puma +0 -0
  11. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  12. data/{tools → docs}/jungle/upstart/README.md +0 -0
  13. data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
  14. data/{tools → docs}/jungle/upstart/puma.conf +0 -0
  15. data/docs/signals.md +7 -6
  16. data/docs/systemd.md +1 -63
  17. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  18. data/ext/puma_http11/extconf.rb +4 -3
  19. data/ext/puma_http11/mini_ssl.c +15 -2
  20. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  21. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  22. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
  23. data/ext/puma_http11/puma_http11.c +6 -38
  24. data/lib/puma.rb +20 -0
  25. data/lib/puma/app/status.rb +14 -1
  26. data/lib/puma/binder.rb +90 -68
  27. data/lib/puma/cli.rb +7 -15
  28. data/lib/puma/client.rb +62 -13
  29. data/lib/puma/cluster.rb +193 -74
  30. data/lib/puma/commonlogger.rb +2 -2
  31. data/lib/puma/configuration.rb +31 -42
  32. data/lib/puma/const.rb +3 -3
  33. data/lib/puma/control_cli.rb +29 -17
  34. data/lib/puma/detect.rb +17 -0
  35. data/lib/puma/dsl.rb +144 -70
  36. data/lib/puma/error_logger.rb +97 -0
  37. data/lib/puma/events.rb +37 -31
  38. data/lib/puma/io_buffer.rb +9 -2
  39. data/lib/puma/jruby_restart.rb +0 -58
  40. data/lib/puma/launcher.rb +57 -31
  41. data/lib/puma/minissl.rb +68 -18
  42. data/lib/puma/minissl/context_builder.rb +0 -3
  43. data/lib/puma/null_io.rb +1 -1
  44. data/lib/puma/plugin.rb +1 -10
  45. data/lib/puma/puma_http11.jar +0 -0
  46. data/lib/puma/rack/builder.rb +0 -4
  47. data/lib/puma/reactor.rb +10 -16
  48. data/lib/puma/runner.rb +8 -36
  49. data/lib/puma/server.rb +161 -218
  50. data/lib/puma/single.rb +8 -64
  51. data/lib/puma/state_file.rb +6 -3
  52. data/lib/puma/thread_pool.rb +116 -51
  53. data/lib/puma/util.rb +1 -0
  54. data/lib/rack/handler/puma.rb +1 -3
  55. data/tools/{docker/Dockerfile → Dockerfile} +0 -0
  56. metadata +17 -19
  57. data/docs/tcp_mode.md +0 -96
  58. data/ext/puma_http11/io_buffer.c +0 -155
  59. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  60. data/lib/puma/tcp_logger.rb +0 -41
  61. data/tools/jungle/README.md +0 -19
  62. data/tools/jungle/init.d/README.md +0 -61
  63. data/tools/jungle/init.d/puma +0 -421
  64. data/tools/jungle/init.d/run-puma +0 -18
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/const'
4
+
5
+ module Puma
6
+ # The implementation of a detailed error logging.
7
+ # @version 5.0.0
8
+ #
9
+ class ErrorLogger
10
+ include Const
11
+
12
+ attr_reader :ioerr
13
+
14
+ REQUEST_FORMAT = %{"%s %s%s" - (%s)}
15
+
16
+ def initialize(ioerr)
17
+ @ioerr = ioerr
18
+ @ioerr.sync = true
19
+
20
+ @debug = ENV.key? 'PUMA_DEBUG'
21
+ end
22
+
23
+ def self.stdio
24
+ new $stderr
25
+ end
26
+
27
+ # Print occured error details.
28
+ # +options+ hash with additional options:
29
+ # - +error+ is an exception object
30
+ # - +req+ the http request
31
+ # - +text+ (default nil) custom string to print in title
32
+ # and before all remaining info.
33
+ #
34
+ def info(options={})
35
+ ioerr.puts title(options)
36
+ end
37
+
38
+ # Print occured error details only if
39
+ # environment variable PUMA_DEBUG is defined.
40
+ # +options+ hash with additional options:
41
+ # - +error+ is an exception object
42
+ # - +req+ the http request
43
+ # - +text+ (default nil) custom string to print in title
44
+ # and before all remaining info.
45
+ #
46
+ def debug(options={})
47
+ return unless @debug
48
+
49
+ error = options[:error]
50
+ req = options[:req]
51
+
52
+ string_block = []
53
+ string_block << title(options)
54
+ string_block << request_dump(req) if request_parsed?(req)
55
+ string_block << error.backtrace if error
56
+
57
+ ioerr.puts string_block.join("\n")
58
+ end
59
+
60
+ def title(options={})
61
+ text = options[:text]
62
+ req = options[:req]
63
+ error = options[:error]
64
+
65
+ string_block = ["#{Time.now}"]
66
+ string_block << " #{text}" if text
67
+ string_block << " (#{request_title(req)})" if request_parsed?(req)
68
+ string_block << ": #{error.inspect}" if error
69
+ string_block.join('')
70
+ end
71
+
72
+ def request_dump(req)
73
+ "Headers: #{request_headers(req)}\n" \
74
+ "Body: #{req.body}"
75
+ end
76
+
77
+ def request_title(req)
78
+ env = req.env
79
+
80
+ REQUEST_FORMAT % [
81
+ env[REQUEST_METHOD],
82
+ env[REQUEST_PATH] || env[PATH_INFO],
83
+ env[QUERY_STRING] || "",
84
+ env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-"
85
+ ]
86
+ end
87
+
88
+ def request_headers(req)
89
+ headers = req.env.select { |key, _| key.start_with?('HTTP_') }
90
+ headers.map { |key, value| [key[5..-1], value] }.to_h.inspect
91
+ end
92
+
93
+ def request_parsed?(req)
94
+ req && req.env[REQUEST_METHOD]
95
+ end
96
+ end
97
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/const'
4
3
  require "puma/null_io"
4
+ require 'puma/error_logger'
5
5
  require 'stringio'
6
6
 
7
7
  module Puma
@@ -23,8 +23,6 @@ module Puma
23
23
  end
24
24
  end
25
25
 
26
- include Const
27
-
28
26
  # Create an Events object that prints to +stdout+ and +stderr+.
29
27
  #
30
28
  def initialize(stdout, stderr)
@@ -36,6 +34,7 @@ module Puma
36
34
  @stderr.sync = true
37
35
 
38
36
  @debug = ENV.key? 'PUMA_DEBUG'
37
+ @error_logger = ErrorLogger.new(@stderr)
39
38
 
40
39
  @hooks = Hash.new { |h,k| h[k] = [] }
41
40
  end
@@ -66,7 +65,8 @@ module Puma
66
65
  # Write +str+ to +@stdout+
67
66
  #
68
67
  def log(str)
69
- @stdout.puts format(str)
68
+ @stdout.puts format(str) if @stdout.respond_to? :puts
69
+ rescue Errno::EPIPE
70
70
  end
71
71
 
72
72
  def write(str)
@@ -80,7 +80,7 @@ module Puma
80
80
  # Write +str+ to +@stderr+
81
81
  #
82
82
  def error(str)
83
- @stderr.puts format("ERROR: #{str}")
83
+ @error_logger.info(text: format("ERROR: #{str}"))
84
84
  exit 1
85
85
  end
86
86
 
@@ -88,43 +88,49 @@ module Puma
88
88
  formatter.call(str)
89
89
  end
90
90
 
91
+ # An HTTP connection error has occurred.
92
+ # +error+ a connection exception, +req+ the request,
93
+ # and +text+ additional info
94
+ # @version 5.0.0
95
+ #
96
+ def connection_error(error, req, text="HTTP connection error")
97
+ @error_logger.info(error: error, req: req, text: text)
98
+ end
99
+
91
100
  # An HTTP parse error has occurred.
92
- # +server+ is the Server object, +env+ the request, and +error+ a
93
- # parsing exception.
101
+ # +error+ a parsing exception,
102
+ # and +req+ the request.
94
103
  #
95
- def parse_error(server, env, error)
96
- @stderr.puts "#{Time.now}: HTTP parse error, malformed request " \
97
- "(#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}#{env[REQUEST_PATH]}): " \
98
- "#{error.inspect}" \
99
- "\n---\n"
104
+ def parse_error(error, req)
105
+ @error_logger.info(error: error, req: req, text: 'HTTP parse error, malformed request')
100
106
  end
101
107
 
102
108
  # An SSL error has occurred.
103
- # +server+ is the Server object, +peeraddr+ peer address, +peercert+
104
- # any peer certificate (if present), and +error+ an exception object.
109
+ # @param error <Puma::MiniSSL::SSLError>
110
+ # @param ssl_socket <Puma::MiniSSL::Socket>
105
111
  #
106
- def ssl_error(server, peeraddr, peercert, error)
112
+ def ssl_error(error, ssl_socket)
113
+ peeraddr = ssl_socket.peeraddr.last rescue "<unknown>"
114
+ peercert = ssl_socket.peercert
107
115
  subject = peercert ? peercert.subject : nil
108
- @stderr.puts "#{Time.now}: SSL error, peer: #{peeraddr}, peer cert: #{subject}, #{error.inspect}"
116
+ @error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
109
117
  end
110
118
 
111
119
  # An unknown error has occurred.
112
- # +server+ is the Server object, +error+ an exception object,
113
- # +kind+ some additional info, and +env+ the request.
120
+ # +error+ an exception object, +req+ the request,
121
+ # and +text+ additional info
114
122
  #
115
- def unknown_error(server, error, kind="Unknown", env=nil)
116
- if error.respond_to? :render
117
- error.render "#{Time.now}: #{kind} error", @stderr
118
- else
119
- if env
120
- string_block = [ "#{Time.now}: #{kind} error handling request { #{env['REQUEST_METHOD']} #{env['PATH_INFO']} }" ]
121
- string_block << error.inspect
122
- else
123
- string_block = [ "#{Time.now}: #{kind} error: #{error.inspect}" ]
124
- end
125
- string_block << error.backtrace
126
- @stderr.puts string_block.join("\n")
127
- end
123
+ def unknown_error(error, req=nil, text="Unknown error")
124
+ @error_logger.info(error: error, req: req, text: text)
125
+ end
126
+
127
+ # Log occurred error debug dump.
128
+ # +error+ an exception object, +req+ the request,
129
+ # and +text+ additional info
130
+ # @version 5.0.0
131
+ #
132
+ def debug_error(error, req=nil, text="")
133
+ @error_logger.debug(error: error, req: req, text: text)
128
134
  end
129
135
 
130
136
  def on_booted(&block)
@@ -1,4 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/detect'
4
- require 'puma/puma_http11'
3
+ module Puma
4
+ class IOBuffer < String
5
+ def append(*args)
6
+ args.each { |a| concat(a) }
7
+ end
8
+
9
+ alias reset clear
10
+ end
11
+ end
@@ -22,63 +22,5 @@ module Puma
22
22
  execlp(cmd, *argv)
23
23
  raise SystemCallError.new(FFI.errno)
24
24
  end
25
-
26
- PermKey = 'PUMA_DAEMON_PERM'
27
- RestartKey = 'PUMA_DAEMON_RESTART'
28
-
29
- # Called to tell things "Your now always in daemon mode,
30
- # don't try to reenter it."
31
- #
32
- def self.perm_daemonize
33
- ENV[PermKey] = "1"
34
- end
35
-
36
- def self.daemon?
37
- ENV.key?(PermKey) || ENV.key?(RestartKey)
38
- end
39
-
40
- def self.daemon_init
41
- return true if ENV.key?(PermKey)
42
-
43
- return false unless ENV.key? RestartKey
44
-
45
- master = ENV[RestartKey]
46
-
47
- # In case the master disappears early
48
- begin
49
- Process.kill "SIGUSR2", master.to_i
50
- rescue SystemCallError => e
51
- end
52
-
53
- ENV[RestartKey] = ""
54
-
55
- setsid
56
-
57
- null = File.open "/dev/null", "w+"
58
- STDIN.reopen null
59
- STDOUT.reopen null
60
- STDERR.reopen null
61
-
62
- true
63
- end
64
-
65
- def self.daemon_start(dir, argv)
66
- ENV[RestartKey] = Process.pid.to_s
67
-
68
- if k = ENV['PUMA_JRUBY_DAEMON_OPTS']
69
- ENV['JRUBY_OPTS'] = k
70
- end
71
-
72
- cmd = argv.first
73
- argv = ([:string] * argv.size).zip(argv).flatten
74
- argv << :string
75
- argv << nil
76
-
77
- chdir(dir)
78
- ret = fork
79
- return ret if ret != 0
80
- execlp(cmd, *argv)
81
- raise SystemCallError.new(FFI.errno)
82
- end
83
25
  end
84
26
  end
@@ -47,8 +47,9 @@ module Puma
47
47
  @original_argv = @argv.dup
48
48
  @config = conf
49
49
 
50
- @binder = Binder.new(@events)
51
- @binder.import_from_env
50
+ @binder = Binder.new(@events, conf)
51
+ @binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
52
+ @binder.create_activated_fds(ENV).each { |k| ENV.delete k }
52
53
 
53
54
  @environment = conf.environment
54
55
 
@@ -69,10 +70,6 @@ module Puma
69
70
  unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
70
71
  end
71
72
 
72
- if @options[:daemon] && Puma.windows?
73
- unsupported 'daemon mode not supported on Windows'
74
- end
75
-
76
73
  Dir.chdir(@restart_dir)
77
74
 
78
75
  prune_bundler if prune_bundler?
@@ -105,6 +102,7 @@ module Puma
105
102
  write_pid
106
103
 
107
104
  path = @options[:state]
105
+ permission = @options[:state_permission]
108
106
  return unless path
109
107
 
110
108
  require 'puma/state_file'
@@ -113,8 +111,9 @@ module Puma
113
111
  sf.pid = Process.pid
114
112
  sf.control_url = @options[:control_url]
115
113
  sf.control_auth_token = @options[:control_auth_token]
114
+ sf.running_from = File.expand_path('.')
116
115
 
117
- sf.save path
116
+ sf.save path, permission
118
117
  end
119
118
 
120
119
  # Delete the configured pidfile
@@ -174,24 +173,28 @@ module Puma
174
173
  case @status
175
174
  when :halt
176
175
  log "* Stopping immediately!"
176
+ @runner.stop_control
177
177
  when :run, :stop
178
178
  graceful_stop
179
179
  when :restart
180
180
  log "* Restarting..."
181
181
  ENV.replace(previous_env)
182
- @runner.before_restart
182
+ @runner.stop_control
183
183
  restart!
184
184
  when :exit
185
185
  # nothing
186
186
  end
187
- @binder.close_unix_paths
187
+ close_binder_listeners unless @status == :restart
188
188
  end
189
189
 
190
- # Return which tcp port the launcher is using, if it's using TCP
191
- def connected_port
192
- @binder.connected_port
190
+ # Return all tcp ports the launcher may be using, TCP or SSL
191
+ # @!attribute [r] connected_ports
192
+ # @version 5.0.0
193
+ def connected_ports
194
+ @binder.connected_ports
193
195
  end
194
196
 
197
+ # @!attribute [r] restart_args
195
198
  def restart_args
196
199
  cmd = @options[:restart_cmd]
197
200
  if cmd
@@ -202,9 +205,23 @@ module Puma
202
205
  end
203
206
 
204
207
  def close_binder_listeners
208
+ @runner.close_control_listeners
205
209
  @binder.close_listeners
206
210
  end
207
211
 
212
+ # @!attribute [r] thread_status
213
+ # @version 5.0.0
214
+ def thread_status
215
+ Thread.list.each do |thread|
216
+ name = "Thread: TID-#{thread.object_id.to_s(36)}"
217
+ name += " #{thread['label']}" if thread['label']
218
+ name += " #{thread.name}" if thread.respond_to?(:name) && thread.name
219
+ backtrace = thread.backtrace || ["<no backtrace available>"]
220
+
221
+ yield name, backtrace
222
+ end
223
+ end
224
+
208
225
  private
209
226
 
210
227
  # If configured, write the pid of the current process out
@@ -225,7 +242,7 @@ module Puma
225
242
  end
226
243
 
227
244
  def restart!
228
- @config.run_hooks :on_restart, self
245
+ @config.run_hooks :on_restart, self, @events
229
246
 
230
247
  if Puma.jruby?
231
248
  close_binder_listeners
@@ -241,11 +258,13 @@ module Puma
241
258
  else
242
259
  argv = restart_args
243
260
  Dir.chdir(@restart_dir)
261
+ ENV.update(@binder.redirects_for_restart_env)
244
262
  argv += [@binder.redirects_for_restart]
245
263
  Kernel.exec(*argv)
246
264
  end
247
265
  end
248
266
 
267
+ # @!attribute [r] dependencies_and_files_to_require_after_prune
249
268
  def dependencies_and_files_to_require_after_prune
250
269
  puma = spec_for_gem("puma")
251
270
 
@@ -256,6 +275,7 @@ module Puma
256
275
  [deps, require_paths_for_gem(puma) + extra_runtime_deps_directories]
257
276
  end
258
277
 
278
+ # @!attribute [r] extra_runtime_deps_directories
259
279
  def extra_runtime_deps_directories
260
280
  Array(@options[:extra_runtime_dependencies]).map do |d_name|
261
281
  if (spec = spec_for_gem(d_name))
@@ -267,6 +287,7 @@ module Puma
267
287
  end.flatten.compact
268
288
  end
269
289
 
290
+ # @!attribute [r] puma_wild_location
270
291
  def puma_wild_location
271
292
  puma = spec_for_gem("puma")
272
293
  dirs = require_paths_for_gem(puma)
@@ -275,6 +296,7 @@ module Puma
275
296
  end
276
297
 
277
298
  def prune_bundler
299
+ return if ENV['PUMA_BUNDLER_PRUNED']
278
300
  return unless defined?(Bundler)
279
301
  require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
280
302
  unless puma_wild_location
@@ -286,8 +308,10 @@ module Puma
286
308
 
287
309
  log '* Pruning Bundler environment'
288
310
  home = ENV['GEM_HOME']
289
- Bundler.with_clean_env do
311
+ bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
312
+ with_unbundled_env do
290
313
  ENV['GEM_HOME'] = home
314
+ ENV['BUNDLE_GEMFILE'] = bundle_gemfile
291
315
  ENV['PUMA_BUNDLER_PRUNED'] = '1'
292
316
  args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
293
317
  # Ruby 2.0+ defaults to true which breaks socket activation
@@ -323,25 +347,11 @@ module Puma
323
347
  log "- Goodbye!"
324
348
  end
325
349
 
326
- def log_thread_status
327
- Thread.list.each do |thread|
328
- log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
329
- logstr = "Thread: TID-#{thread.object_id.to_s(36)}"
330
- logstr += " #{thread.name}" if thread.respond_to?(:name)
331
- log logstr
332
-
333
- if thread.backtrace
334
- log thread.backtrace.join("\n")
335
- else
336
- log "<no backtrace available>"
337
- end
338
- end
339
- end
340
-
341
350
  def set_process_title
342
351
  Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
343
352
  end
344
353
 
354
+ # @!attribute [r] title
345
355
  def title
346
356
  buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
347
357
  buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
@@ -353,6 +363,7 @@ module Puma
353
363
  ENV['RACK_ENV'] = environment
354
364
  end
355
365
 
366
+ # @!attribute [r] environment
356
367
  def environment
357
368
  @environment
358
369
  end
@@ -456,8 +467,13 @@ module Puma
456
467
  end
457
468
 
458
469
  begin
459
- Signal.trap "SIGINFO" do
460
- log_thread_status
470
+ unless Puma.jruby? # INFO in use by JVM already
471
+ Signal.trap "SIGINFO" do
472
+ thread_status do |name, backtrace|
473
+ @events.log name
474
+ @events.log backtrace.map { |bt| " #{bt}" }
475
+ end
476
+ end
461
477
  end
462
478
  rescue Exception
463
479
  # Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
@@ -471,5 +487,15 @@ module Puma
471
487
  raise "#{feature} is not supported on your version of RubyGems. " \
472
488
  "You must have RubyGems #{min_version}+ to use this feature."
473
489
  end
490
+
491
+ # @version 5.0.0
492
+ def with_unbundled_env
493
+ bundler_ver = Gem::Version.new(Bundler::VERSION)
494
+ if bundler_ver < Gem::Version.new('2.1.0')
495
+ Bundler.with_clean_env { yield }
496
+ else
497
+ Bundler.with_unbundled_env { yield }
498
+ end
499
+ end
474
500
  end
475
501
  end