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
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '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
+ LOG_QUEUE = Queue.new
17
+
18
+ def initialize(ioerr)
19
+ @ioerr = ioerr
20
+
21
+ @debug = ENV.key? 'PUMA_DEBUG'
22
+ end
23
+
24
+ def self.stdio
25
+ new $stderr
26
+ end
27
+
28
+ # Print occurred error details.
29
+ # +options+ hash with additional options:
30
+ # - +error+ is an exception object
31
+ # - +req+ the http request
32
+ # - +text+ (default nil) custom string to print in title
33
+ # and before all remaining info.
34
+ #
35
+ def info(options={})
36
+ internal_write title(options)
37
+ end
38
+
39
+ # Print occurred error details only if
40
+ # environment variable PUMA_DEBUG is defined.
41
+ # +options+ hash with additional options:
42
+ # - +error+ is an exception object
43
+ # - +req+ the http request
44
+ # - +text+ (default nil) custom string to print in title
45
+ # and before all remaining info.
46
+ #
47
+ def debug(options={})
48
+ return unless @debug
49
+
50
+ error = options[:error]
51
+ req = options[:req]
52
+
53
+ string_block = []
54
+ string_block << title(options)
55
+ string_block << request_dump(req) if request_parsed?(req)
56
+ string_block << error.backtrace if error
57
+
58
+ internal_write string_block.join("\n")
59
+ end
60
+
61
+ def title(options={})
62
+ text = options[:text]
63
+ req = options[:req]
64
+ error = options[:error]
65
+
66
+ string_block = ["#{Time.now}"]
67
+ string_block << " #{text}" if text
68
+ string_block << " (#{request_title(req)})" if request_parsed?(req)
69
+ string_block << ": #{error.inspect}" if error
70
+ string_block.join('')
71
+ end
72
+
73
+ def request_dump(req)
74
+ "Headers: #{request_headers(req)}\n" \
75
+ "Body: #{req.body}"
76
+ end
77
+
78
+ def request_title(req)
79
+ env = req.env
80
+
81
+ REQUEST_FORMAT % [
82
+ env[REQUEST_METHOD],
83
+ env[REQUEST_PATH] || env[PATH_INFO],
84
+ env[QUERY_STRING] || "",
85
+ env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-"
86
+ ]
87
+ end
88
+
89
+ def request_headers(req)
90
+ headers = req.env.select { |key, _| key.start_with?('HTTP_') }
91
+ headers.map { |key, value| [key[5..-1], value] }.to_h.inspect
92
+ end
93
+
94
+ def request_parsed?(req)
95
+ req && req.env[REQUEST_METHOD]
96
+ end
97
+
98
+ def internal_write(str)
99
+ LOG_QUEUE << str
100
+ while (w_str = LOG_QUEUE.pop(true)) do
101
+ begin
102
+ @ioerr.is_a?(IO) and @ioerr.wait_writable(1)
103
+ @ioerr.write "#{w_str}\n"
104
+ @ioerr.flush unless @ioerr.sync
105
+ rescue Errno::EPIPE, Errno::EBADF, IOError
106
+ end
107
+ end
108
+ rescue ThreadError
109
+ end
110
+ private :internal_write
111
+ end
112
+ end
data/lib/puma/events.rb CHANGED
@@ -1,54 +1,23 @@
1
- require 'puma/const'
2
- require "puma/null_io"
3
- require 'stringio'
1
+ # frozen_string_literal: true
4
2
 
5
3
  module Puma
6
- # The default implement of an event sink object used by Server
7
- # for when certain kinds of events occur in the life of the server.
8
- #
9
- # The methods available are the events that the Server fires.
10
- #
11
- class Events
12
- class DefaultFormatter
13
- def call(str)
14
- str
15
- end
16
- end
17
-
18
- class PidFormatter
19
- def call(str)
20
- "[#{$$}] #{str}"
21
- end
22
- end
23
-
24
- include Const
25
-
26
- # Create an Events object that prints to +stdout+ and +stderr+.
27
- #
28
- def initialize(stdout, stderr)
29
- @formatter = DefaultFormatter.new
30
- @stdout = stdout
31
- @stderr = stderr
32
4
 
33
- @stdout.sync = true
34
- @stderr.sync = true
35
-
36
- @debug = ENV.key? 'PUMA_DEBUG'
5
+ # This is an event sink used by `Puma::Server` to handle
6
+ # lifecycle events such as :on_booted, :on_restart, and :on_stopped.
7
+ # Using `Puma::DSL` it is possible to register callback hooks
8
+ # for each event type.
9
+ class Events
37
10
 
11
+ def initialize
38
12
  @hooks = Hash.new { |h,k| h[k] = [] }
39
13
  end
40
14
 
41
- attr_reader :stdout, :stderr
42
- attr_accessor :formatter
43
-
44
15
  # Fire callbacks for the named hook
45
- #
46
16
  def fire(hook, *args)
47
17
  @hooks[hook].each { |t| t.call(*args) }
48
18
  end
49
19
 
50
20
  # Register a callback for a given hook
51
- #
52
21
  def register(hook, obj=nil, &blk)
53
22
  if obj and blk
54
23
  raise "Specify either an object or a block, not both"
@@ -61,91 +30,28 @@ module Puma
61
30
  h
62
31
  end
63
32
 
64
- # Write +str+ to +@stdout+
65
- #
66
- def log(str)
67
- @stdout.puts format(str)
68
- end
69
-
70
- def write(str)
71
- @stdout.write format(str)
72
- end
73
-
74
- def debug(str)
75
- log("% #{str}") if @debug
76
- end
77
-
78
- # Write +str+ to +@stderr+
79
- #
80
- def error(str)
81
- @stderr.puts format("ERROR: #{str}")
82
- exit 1
83
- end
84
-
85
- def format(str)
86
- formatter.call(str)
87
- end
88
-
89
- # An HTTP parse error has occurred.
90
- # +server+ is the Server object, +env+ the request, and +error+ a
91
- # parsing exception.
92
- #
93
- def parse_error(server, env, error)
94
- @stderr.puts "#{Time.now}: HTTP parse error, malformed request (#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}): #{error.inspect}\n---\n"
95
- end
96
-
97
- # An SSL error has occurred.
98
- # +server+ is the Server object, +peeraddr+ peer address, +peercert+
99
- # any peer certificate (if present), and +error+ an exception object.
100
- #
101
- def ssl_error(server, peeraddr, peercert, error)
102
- subject = peercert ? peercert.subject : nil
103
- @stderr.puts "#{Time.now}: SSL error, peer: #{peeraddr}, peer cert: #{subject}, #{error.inspect}"
33
+ def on_booted(&block)
34
+ register(:on_booted, &block)
104
35
  end
105
36
 
106
- # An unknown error has occurred.
107
- # +server+ is the Server object, +error+ an exception object,
108
- # +kind+ some additional info, and +env+ the request.
109
- #
110
- def unknown_error(server, error, kind="Unknown", env=nil)
111
- if error.respond_to? :render
112
- error.render "#{Time.now}: #{kind} error", @stderr
113
- else
114
- if env
115
- string_block = [ "#{Time.now}: #{kind} error handling request { #{env['REQUEST_METHOD']} #{env['PATH_INFO']} }" ]
116
- string_block << error.inspect
117
- else
118
- string_block = [ "#{Time.now}: #{kind} error: #{error.inspect}" ]
119
- end
120
- string_block << error.backtrace
121
- @stderr.puts string_block.join("\n")
122
- end
37
+ def on_restart(&block)
38
+ register(:on_restart, &block)
123
39
  end
124
40
 
125
- def on_booted(&block)
126
- register(:on_booted, &block)
41
+ def on_stopped(&block)
42
+ register(:on_stopped, &block)
127
43
  end
128
44
 
129
45
  def fire_on_booted!
130
46
  fire(:on_booted)
131
47
  end
132
48
 
133
- DEFAULT = new(STDOUT, STDERR)
134
-
135
- # Returns an Events object which writes its status to 2 StringIO
136
- # objects.
137
- #
138
- def self.strings
139
- Events.new StringIO.new, StringIO.new
140
- end
141
-
142
- def self.stdio
143
- Events.new $stdout, $stderr
49
+ def fire_on_restart!
50
+ fire(:on_restart)
144
51
  end
145
52
 
146
- def self.null
147
- n = NullIO.new
148
- Events.new n, n
53
+ def fire_on_stopped!
54
+ fire(:on_stopped)
149
55
  end
150
56
  end
151
57
  end
@@ -1,7 +1,46 @@
1
- require 'puma/detect'
1
+ # frozen_string_literal: true
2
2
 
3
- if Puma.jruby?
4
- require 'puma/java_io_buffer'
5
- else
6
- require 'puma/puma_http11'
3
+ require 'stringio'
4
+
5
+ module Puma
6
+ class IOBuffer < StringIO
7
+ def initialize
8
+ super.binmode
9
+ end
10
+
11
+ def empty?
12
+ length.zero?
13
+ end
14
+
15
+ def reset
16
+ truncate 0
17
+ rewind
18
+ end
19
+
20
+ def to_s
21
+ rewind
22
+ read
23
+ end
24
+
25
+ # Read & Reset - returns contents and resets
26
+ # @return [String] StringIO contents
27
+ def read_and_reset
28
+ rewind
29
+ str = read
30
+ truncate 0
31
+ rewind
32
+ str
33
+ end
34
+
35
+ alias_method :clear, :reset
36
+
37
+ # before Ruby 2.5, `write` would only take one argument
38
+ if RUBY_VERSION >= '2.5' && RUBY_ENGINE != 'truffleruby'
39
+ alias_method :append, :write
40
+ else
41
+ def append(*strs)
42
+ strs.each { |str| write str }
43
+ end
44
+ end
45
+ end
7
46
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ffi'
2
4
 
3
5
  module Puma
@@ -14,67 +16,10 @@ module Puma
14
16
  def self.chdir_exec(dir, argv)
15
17
  chdir(dir)
16
18
  cmd = argv.first
17
- argv = ([:string] * argv.size).zip(argv).flatten
18
- argv << :string
19
- argv << nil
20
- execlp(cmd, *argv)
21
- raise SystemCallError.new(FFI.errno)
22
- end
23
-
24
- PermKey = 'PUMA_DAEMON_PERM'
25
- RestartKey = 'PUMA_DAEMON_RESTART'
26
-
27
- # Called to tell things "Your now always in daemon mode,
28
- # don't try to reenter it."
29
- #
30
- def self.perm_daemonize
31
- ENV[PermKey] = "1"
32
- end
33
-
34
- def self.daemon?
35
- ENV.key?(PermKey) || ENV.key?(RestartKey)
36
- end
37
-
38
- def self.daemon_init
39
- return true if ENV.key?(PermKey)
40
-
41
- return false unless ENV.key? RestartKey
42
-
43
- master = ENV[RestartKey]
44
-
45
- # In case the master disappears early
46
- begin
47
- Process.kill "SIGUSR2", master.to_i
48
- rescue SystemCallError => e
49
- end
50
-
51
- ENV[RestartKey] = ""
52
-
53
- setsid
54
-
55
- null = File.open "/dev/null", "w+"
56
- STDIN.reopen null
57
- STDOUT.reopen null
58
- STDERR.reopen null
59
-
60
- true
61
- end
62
-
63
- def self.daemon_start(dir, argv)
64
- ENV[RestartKey] = Process.pid.to_s
65
-
66
- if k = ENV['PUMA_JRUBY_DAEMON_OPTS']
67
- ENV['JRUBY_OPTS'] = k
68
- end
69
-
70
- cmd = argv.first
71
- argv = ([:string] * argv.size).zip(argv).flatten
19
+ argv = ([:string] * argv.size).zip(argv)
20
+ argv.flatten!
72
21
  argv << :string
73
22
  argv << nil
74
-
75
- chdir(dir)
76
- ret = fork
77
- return ret if ret != 0
78
23
  execlp(cmd, *argv)
79
24
  raise SystemCallError.new(FFI.errno)
80
25
  end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+ require 'stringio'
3
+
4
+ module Puma
5
+
6
+ # Puma deliberately avoids the use of the json gem and instead performs JSON
7
+ # serialization without any external dependencies. In a puma cluster, loading
8
+ # any gem into the puma master process means that operators cannot use a
9
+ # phased restart to upgrade their application if the new version of that
10
+ # application uses a different version of that gem. The json gem in
11
+ # particular is additionally problematic because it leverages native
12
+ # extensions. If the puma master process relies on a gem with native
13
+ # extensions and operators remove gems from disk related to old releases,
14
+ # subsequent phased restarts can fail.
15
+ #
16
+ # The implementation of JSON serialization in this module is not designed to
17
+ # be particularly full-featured or fast. It just has to handle the few places
18
+ # where Puma relies on JSON serialization internally.
19
+
20
+ module JSONSerialization
21
+ QUOTE = /"/
22
+ BACKSLASH = /\\/
23
+ CONTROL_CHAR_TO_ESCAPE = /[\x00-\x1F]/ # As required by ECMA-404
24
+ CHAR_TO_ESCAPE = Regexp.union QUOTE, BACKSLASH, CONTROL_CHAR_TO_ESCAPE
25
+
26
+ class SerializationError < StandardError; end
27
+
28
+ class << self
29
+ def generate(value)
30
+ StringIO.open do |io|
31
+ serialize_value io, value
32
+ io.string
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def serialize_value(output, value)
39
+ case value
40
+ when Hash
41
+ output << '{'
42
+ value.each_with_index do |(k, v), index|
43
+ output << ',' if index != 0
44
+ serialize_object_key output, k
45
+ output << ':'
46
+ serialize_value output, v
47
+ end
48
+ output << '}'
49
+ when Array
50
+ output << '['
51
+ value.each_with_index do |member, index|
52
+ output << ',' if index != 0
53
+ serialize_value output, member
54
+ end
55
+ output << ']'
56
+ when Integer, Float
57
+ output << value.to_s
58
+ when String
59
+ serialize_string output, value
60
+ when true
61
+ output << 'true'
62
+ when false
63
+ output << 'false'
64
+ when nil
65
+ output << 'null'
66
+ else
67
+ raise SerializationError, "Unexpected value of type #{value.class}"
68
+ end
69
+ end
70
+
71
+ def serialize_string(output, value)
72
+ output << '"'
73
+ output << value.gsub(CHAR_TO_ESCAPE) do |character|
74
+ case character
75
+ when BACKSLASH
76
+ '\\\\'
77
+ when QUOTE
78
+ '\\"'
79
+ when CONTROL_CHAR_TO_ESCAPE
80
+ '\u%.4X' % character.ord
81
+ end
82
+ end
83
+ output << '"'
84
+ end
85
+
86
+ def serialize_object_key(output, value)
87
+ case value
88
+ when Symbol, String
89
+ serialize_string output, value.to_s
90
+ else
91
+ raise SerializationError, "Could not serialize object of type #{value.class} as object key"
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+ class Launcher
5
+
6
+ # This class is used to pickup Gemfile changes during
7
+ # application restarts.
8
+ class BundlePruner
9
+
10
+ def initialize(original_argv, extra_runtime_dependencies, log_writer)
11
+ @original_argv = Array(original_argv)
12
+ @extra_runtime_dependencies = Array(extra_runtime_dependencies)
13
+ @log_writer = log_writer
14
+ end
15
+
16
+ def prune
17
+ return if ENV['PUMA_BUNDLER_PRUNED']
18
+ return unless defined?(Bundler)
19
+
20
+ require_rubygems_min_version!
21
+
22
+ unless puma_wild_path
23
+ log "! Unable to prune Bundler environment, continuing"
24
+ return
25
+ end
26
+
27
+ dirs = paths_to_require_after_prune
28
+
29
+ log '* Pruning Bundler environment'
30
+ home = ENV['GEM_HOME']
31
+ bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
32
+ bundle_app_config = Bundler.original_env['BUNDLE_APP_CONFIG']
33
+
34
+ with_unbundled_env do
35
+ ENV['GEM_HOME'] = home
36
+ ENV['BUNDLE_GEMFILE'] = bundle_gemfile
37
+ ENV['PUMA_BUNDLER_PRUNED'] = '1'
38
+ ENV["BUNDLE_APP_CONFIG"] = bundle_app_config
39
+ args = [Gem.ruby, puma_wild_path, '-I', dirs.join(':')] + @original_argv
40
+ # Ruby 2.0+ defaults to true which breaks socket activation
41
+ args += [{:close_others => false}]
42
+ Kernel.exec(*args)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def require_rubygems_min_version!
49
+ min_version = Gem::Version.new('2.2')
50
+
51
+ return if min_version <= Gem::Version.new(Gem::VERSION)
52
+
53
+ raise "prune_bundler is not supported on your version of RubyGems. " \
54
+ "You must have RubyGems #{min_version}+ to use this feature."
55
+ end
56
+
57
+ def puma_wild_path
58
+ puma_lib_dir = puma_require_paths.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
59
+ File.expand_path(File.join(puma_lib_dir, '../bin/puma-wild'))
60
+ end
61
+
62
+ def with_unbundled_env
63
+ bundler_ver = Gem::Version.new(Bundler::VERSION)
64
+ if bundler_ver < Gem::Version.new('2.1.0')
65
+ Bundler.with_clean_env { yield }
66
+ else
67
+ Bundler.with_unbundled_env { yield }
68
+ end
69
+ end
70
+
71
+ def paths_to_require_after_prune
72
+ puma_require_paths + extra_runtime_deps_paths
73
+ end
74
+
75
+ def extra_runtime_deps_paths
76
+ t = @extra_runtime_dependencies.map do |dep_name|
77
+ if (spec = spec_for_gem(dep_name))
78
+ require_paths_for_gem(spec)
79
+ else
80
+ log "* Could not load extra dependency: #{dep_name}"
81
+ nil
82
+ end
83
+ end
84
+ t.flatten!; t.compact!; t
85
+ end
86
+
87
+ def puma_require_paths
88
+ require_paths_for_gem(spec_for_gem('puma'))
89
+ end
90
+
91
+ def spec_for_gem(gem_name)
92
+ Bundler.rubygems.loaded_specs(gem_name)
93
+ end
94
+
95
+ def require_paths_for_gem(gem_spec)
96
+ gem_spec.full_require_paths
97
+ end
98
+
99
+ def log(str)
100
+ @log_writer.log(str)
101
+ end
102
+ end
103
+ end
104
+ end