puma 4.3.3-java → 5.0.0.beta2-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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +79 -8
  3. data/LICENSE +23 -20
  4. data/README.md +18 -12
  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 +5 -4
  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/http11_parser.c +3 -1
  20. data/ext/puma_http11/http11_parser.rl +3 -1
  21. data/ext/puma_http11/mini_ssl.c +12 -2
  22. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  23. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +37 -6
  24. data/ext/puma_http11/puma_http11.c +3 -38
  25. data/lib/puma.rb +5 -0
  26. data/lib/puma/app/status.rb +18 -3
  27. data/lib/puma/binder.rb +66 -63
  28. data/lib/puma/cli.rb +7 -15
  29. data/lib/puma/client.rb +64 -14
  30. data/lib/puma/cluster.rb +183 -74
  31. data/lib/puma/commonlogger.rb +2 -2
  32. data/lib/puma/configuration.rb +30 -42
  33. data/lib/puma/const.rb +2 -3
  34. data/lib/puma/control_cli.rb +27 -17
  35. data/lib/puma/detect.rb +8 -0
  36. data/lib/puma/dsl.rb +72 -36
  37. data/lib/puma/error_logger.rb +96 -0
  38. data/lib/puma/events.rb +33 -31
  39. data/lib/puma/io_buffer.rb +9 -2
  40. data/lib/puma/jruby_restart.rb +0 -58
  41. data/lib/puma/launcher.rb +46 -31
  42. data/lib/puma/minissl.rb +47 -10
  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 +8 -3
  48. data/lib/puma/runner.rb +6 -35
  49. data/lib/puma/server.rb +138 -182
  50. data/lib/puma/single.rb +7 -64
  51. data/lib/puma/state_file.rb +6 -3
  52. data/lib/puma/thread_pool.rb +90 -49
  53. data/lib/rack/handler/puma.rb +1 -3
  54. data/tools/{docker/Dockerfile → Dockerfile} +0 -0
  55. metadata +18 -21
  56. data/docs/tcp_mode.md +0 -96
  57. data/ext/puma_http11/io_buffer.c +0 -155
  58. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  59. data/lib/puma/tcp_logger.rb +0 -41
  60. data/tools/jungle/README.md +0 -19
  61. data/tools/jungle/init.d/README.md +0 -61
  62. data/tools/jungle/init.d/puma +0 -421
  63. data/tools/jungle/init.d/run-puma +0 -18
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/const'
4
+
5
+ module Puma
6
+ # The implementation of a detailed error logging.
7
+ #
8
+ class ErrorLogger
9
+ include Const
10
+
11
+ attr_reader :ioerr
12
+
13
+ REQUEST_FORMAT = %{"%s %s%s" - (%s)}
14
+
15
+ def initialize(ioerr)
16
+ @ioerr = ioerr
17
+ @ioerr.sync = true
18
+
19
+ @debug = ENV.key? 'PUMA_DEBUG'
20
+ end
21
+
22
+ def self.stdio
23
+ new $stderr
24
+ end
25
+
26
+ # Print occured error details.
27
+ # +options+ hash with additional options:
28
+ # - +error+ is an exception object
29
+ # - +req+ the http request
30
+ # - +text+ (default nil) custom string to print in title
31
+ # and before all remaining info.
32
+ #
33
+ def info(options={})
34
+ ioerr.puts title(options)
35
+ end
36
+
37
+ # Print occured error details only if
38
+ # environment variable PUMA_DEBUG is defined.
39
+ # +options+ hash with additional options:
40
+ # - +error+ is an exception object
41
+ # - +req+ the http request
42
+ # - +text+ (default nil) custom string to print in title
43
+ # and before all remaining info.
44
+ #
45
+ def debug(options={})
46
+ return unless @debug
47
+
48
+ error = options[:error]
49
+ req = options[:req]
50
+
51
+ string_block = []
52
+ string_block << title(options)
53
+ string_block << request_dump(req) if req
54
+ string_block << error_backtrace(options) if error
55
+
56
+ ioerr.puts string_block.join("\n")
57
+ end
58
+
59
+ def title(options={})
60
+ text = options[:text]
61
+ req = options[:req]
62
+ error = options[:error]
63
+
64
+ string_block = ["#{Time.now}"]
65
+ string_block << " #{text}" if text
66
+ string_block << " (#{request_title(req)})" if request_parsed?(req)
67
+ string_block << ": #{error.inspect}" if error
68
+ string_block.join('')
69
+ end
70
+
71
+ def request_dump(req)
72
+ "Headers: #{request_headers(req)}\n" \
73
+ "Body: #{req.body}"
74
+ end
75
+
76
+ def request_title(req)
77
+ env = req.env
78
+
79
+ REQUEST_FORMAT % [
80
+ env[REQUEST_METHOD],
81
+ env[REQUEST_PATH] || env[PATH_INFO],
82
+ env[QUERY_STRING] || "",
83
+ env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-"
84
+ ]
85
+ end
86
+
87
+ def request_headers(req)
88
+ headers = req.env.select { |key, _| key.start_with?('HTTP_') }
89
+ headers.map { |key, value| [key[5..-1], value] }.to_h.inspect
90
+ end
91
+
92
+ def request_parsed?(req)
93
+ req && req.env[REQUEST_METHOD]
94
+ end
95
+ end
96
+ 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,45 @@ 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
+ #
95
+ def connection_error(error, req, text="HTTP connection error")
96
+ @error_logger.info(error: error, req: req, text: text)
97
+ end
98
+
91
99
  # An HTTP parse error has occurred.
92
- # +server+ is the Server object, +env+ the request, and +error+ a
93
- # parsing exception.
100
+ # +error+ a parsing exception,
101
+ # and +req+ the request.
94
102
  #
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"
103
+ def parse_error(error, req)
104
+ @error_logger.info(error: error, req: req, text: 'HTTP parse error, malformed request')
100
105
  end
101
106
 
102
107
  # 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.
108
+ # +error+ an exception object, +peeraddr+ peer address,
109
+ # and +peercert+ any peer certificate (if present).
105
110
  #
106
- def ssl_error(server, peeraddr, peercert, error)
111
+ def ssl_error(error, peeraddr, peercert)
107
112
  subject = peercert ? peercert.subject : nil
108
- @stderr.puts "#{Time.now}: SSL error, peer: #{peeraddr}, peer cert: #{subject}, #{error.inspect}"
113
+ @error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
109
114
  end
110
115
 
111
116
  # An unknown error has occurred.
112
- # +server+ is the Server object, +error+ an exception object,
113
- # +kind+ some additional info, and +env+ the request.
117
+ # +error+ an exception object, +req+ the request,
118
+ # and +text+ additional info
114
119
  #
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
120
+ def unknown_error(error, req=nil, text="Unknown error")
121
+ @error_logger.info(error: error, req: req, text: text)
122
+ end
123
+
124
+ # Log occurred error debug dump.
125
+ # +error+ an exception object, +req+ the request,
126
+ # and +text+ additional info
127
+ #
128
+ def debug_error(error, req=nil, text="")
129
+ @error_logger.debug(error: error, req: req, text: text)
128
130
  end
129
131
 
130
132
  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,22 +173,23 @@ 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
+ def connected_ports
192
+ @binder.connected_ports
193
193
  end
194
194
 
195
195
  def restart_args
@@ -202,9 +202,21 @@ module Puma
202
202
  end
203
203
 
204
204
  def close_binder_listeners
205
+ @runner.close_control_listeners
205
206
  @binder.close_listeners
206
207
  end
207
208
 
209
+ def thread_status
210
+ Thread.list.each do |thread|
211
+ name = "Thread: TID-#{thread.object_id.to_s(36)}"
212
+ name += " #{thread['label']}" if thread['label']
213
+ name += " #{thread.name}" if thread.respond_to?(:name) && thread.name
214
+ backtrace = thread.backtrace || ["<no backtrace available>"]
215
+
216
+ yield name, backtrace
217
+ end
218
+ end
219
+
208
220
  private
209
221
 
210
222
  # If configured, write the pid of the current process out
@@ -225,7 +237,7 @@ module Puma
225
237
  end
226
238
 
227
239
  def restart!
228
- @config.run_hooks :on_restart, self
240
+ @config.run_hooks :on_restart, self, @events
229
241
 
230
242
  if Puma.jruby?
231
243
  close_binder_listeners
@@ -241,6 +253,7 @@ module Puma
241
253
  else
242
254
  argv = restart_args
243
255
  Dir.chdir(@restart_dir)
256
+ ENV.update(@binder.redirects_for_restart_env)
244
257
  argv += [@binder.redirects_for_restart]
245
258
  Kernel.exec(*argv)
246
259
  end
@@ -275,6 +288,7 @@ module Puma
275
288
  end
276
289
 
277
290
  def prune_bundler
291
+ return if ENV['PUMA_BUNDLER_PRUNED']
278
292
  return unless defined?(Bundler)
279
293
  require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
280
294
  unless puma_wild_location
@@ -286,8 +300,10 @@ module Puma
286
300
 
287
301
  log '* Pruning Bundler environment'
288
302
  home = ENV['GEM_HOME']
289
- Bundler.with_clean_env do
303
+ bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
304
+ with_unbundled_env do
290
305
  ENV['GEM_HOME'] = home
306
+ ENV['BUNDLE_GEMFILE'] = bundle_gemfile
291
307
  ENV['PUMA_BUNDLER_PRUNED'] = '1'
292
308
  args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
293
309
  # Ruby 2.0+ defaults to true which breaks socket activation
@@ -323,21 +339,6 @@ module Puma
323
339
  log "- Goodbye!"
324
340
  end
325
341
 
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
342
  def set_process_title
342
343
  Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
343
344
  end
@@ -456,8 +457,13 @@ module Puma
456
457
  end
457
458
 
458
459
  begin
459
- Signal.trap "SIGINFO" do
460
- log_thread_status
460
+ unless Puma.jruby? # INFO in use by JVM already
461
+ Signal.trap "SIGINFO" do
462
+ thread_status do |name, backtrace|
463
+ @events.log name
464
+ @events.log backtrace.map { |bt| " #{bt}" }
465
+ end
466
+ end
461
467
  end
462
468
  rescue Exception
463
469
  # Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
@@ -471,5 +477,14 @@ module Puma
471
477
  raise "#{feature} is not supported on your version of RubyGems. " \
472
478
  "You must have RubyGems #{min_version}+ to use this feature."
473
479
  end
480
+
481
+ def with_unbundled_env
482
+ bundler_ver = Gem::Version.new(Bundler::VERSION)
483
+ if bundler_ver < Gem::Version.new('2.1.0')
484
+ Bundler.with_clean_env { yield }
485
+ else
486
+ Bundler.with_unbundled_env { yield }
487
+ end
488
+ end
474
489
  end
475
490
  end