puma 3.12.6 → 6.2.2

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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1775 -451
  3. data/LICENSE +23 -20
  4. data/README.md +193 -65
  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 +94 -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 +103 -117
  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 +3 -3
  34. data/ext/puma_http11/http11_parser_common.rl +6 -6
  35. data/ext/puma_http11/mini_ssl.c +361 -99
  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 +248 -92
  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 +242 -150
  43. data/lib/puma/cli.rb +38 -34
  44. data/lib/puma/client.rb +387 -244
  45. data/lib/puma/cluster/worker.rb +180 -0
  46. data/lib/puma/cluster/worker_handle.rb +97 -0
  47. data/lib/puma/cluster.rb +261 -243
  48. data/lib/puma/commonlogger.rb +21 -14
  49. data/lib/puma/configuration.rb +116 -88
  50. data/lib/puma/const.rb +101 -100
  51. data/lib/puma/control_cli.rb +115 -70
  52. data/lib/puma/detect.rb +33 -2
  53. data/lib/puma/dsl.rb +731 -134
  54. data/lib/puma/error_logger.rb +113 -0
  55. data/lib/puma/events.rb +16 -112
  56. data/lib/puma/io_buffer.rb +42 -5
  57. data/lib/puma/jruby_restart.rb +2 -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 +184 -133
  61. data/lib/puma/log_writer.rb +147 -0
  62. data/lib/puma/minissl/context_builder.rb +92 -0
  63. data/lib/puma/minissl.rb +246 -70
  64. data/lib/puma/null_io.rb +18 -1
  65. data/lib/puma/plugin/systemd.rb +90 -0
  66. data/lib/puma/plugin/tmp_restart.rb +3 -1
  67. data/lib/puma/plugin.rb +7 -13
  68. data/lib/puma/rack/builder.rb +7 -9
  69. data/lib/puma/rack/urlmap.rb +2 -0
  70. data/lib/puma/rack_default.rb +21 -4
  71. data/lib/puma/reactor.rb +85 -316
  72. data/lib/puma/request.rb +665 -0
  73. data/lib/puma/runner.rb +94 -69
  74. data/lib/puma/sd_notify.rb +149 -0
  75. data/lib/puma/server.rb +314 -771
  76. data/lib/puma/single.rb +20 -74
  77. data/lib/puma/state_file.rb +45 -8
  78. data/lib/puma/thread_pool.rb +142 -92
  79. data/lib/puma/util.rb +22 -10
  80. data/lib/puma.rb +60 -5
  81. data/lib/rack/handler/puma.rb +113 -91
  82. data/tools/Dockerfile +16 -0
  83. data/tools/trickletest.rb +0 -1
  84. metadata +54 -32
  85. data/ext/puma_http11/io_buffer.c +0 -155
  86. data/lib/puma/accept_nonblock.rb +0 -23
  87. data/lib/puma/compat.rb +0 -14
  88. data/lib/puma/convenient.rb +0 -25
  89. data/lib/puma/daemon_ext.rb +0 -33
  90. data/lib/puma/delegation.rb +0 -13
  91. data/lib/puma/java_io_buffer.rb +0 -47
  92. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  93. data/lib/puma/tcp_logger.rb +0 -41
  94. data/tools/jungle/README.md +0 -19
  95. data/tools/jungle/init.d/README.md +0 -61
  96. data/tools/jungle/init.d/puma +0 -421
  97. data/tools/jungle/init.d/run-puma +0 -18
  98. data/tools/jungle/upstart/README.md +0 -61
  99. data/tools/jungle/upstart/puma-manager.conf +0 -31
  100. data/tools/jungle/upstart/puma.conf +0 -69
@@ -0,0 +1,113 @@
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, Errno::EINVAL
106
+ # 'Invalid argument' (Errno::EINVAL) may be raised by flush
107
+ end
108
+ end
109
+ rescue ThreadError
110
+ end
111
+ private :internal_write
112
+ end
113
+ end
data/lib/puma/events.rb CHANGED
@@ -1,56 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/const'
4
- require "puma/null_io"
5
- require 'stringio'
6
-
7
3
  module Puma
8
- # The default implement of an event sink object used by Server
9
- # for when certain kinds of events occur in the life of the server.
10
- #
11
- # The methods available are the events that the Server fires.
12
- #
13
- class Events
14
- class DefaultFormatter
15
- def call(str)
16
- str
17
- end
18
- end
19
-
20
- class PidFormatter
21
- def call(str)
22
- "[#{$$}] #{str}"
23
- end
24
- end
25
-
26
- include Const
27
4
 
28
- # Create an Events object that prints to +stdout+ and +stderr+.
29
- #
30
- def initialize(stdout, stderr)
31
- @formatter = DefaultFormatter.new
32
- @stdout = stdout
33
- @stderr = stderr
34
-
35
- @stdout.sync = true
36
- @stderr.sync = true
37
-
38
- @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
39
10
 
11
+ def initialize
40
12
  @hooks = Hash.new { |h,k| h[k] = [] }
41
13
  end
42
14
 
43
- attr_reader :stdout, :stderr
44
- attr_accessor :formatter
45
-
46
15
  # Fire callbacks for the named hook
47
- #
48
16
  def fire(hook, *args)
49
17
  @hooks[hook].each { |t| t.call(*args) }
50
18
  end
51
19
 
52
20
  # Register a callback for a given hook
53
- #
54
21
  def register(hook, obj=nil, &blk)
55
22
  if obj and blk
56
23
  raise "Specify either an object or a block, not both"
@@ -63,91 +30,28 @@ module Puma
63
30
  h
64
31
  end
65
32
 
66
- # Write +str+ to +@stdout+
67
- #
68
- def log(str)
69
- @stdout.puts format(str)
70
- end
71
-
72
- def write(str)
73
- @stdout.write format(str)
74
- end
75
-
76
- def debug(str)
77
- log("% #{str}") if @debug
78
- end
79
-
80
- # Write +str+ to +@stderr+
81
- #
82
- def error(str)
83
- @stderr.puts format("ERROR: #{str}")
84
- exit 1
85
- end
86
-
87
- def format(str)
88
- formatter.call(str)
89
- end
90
-
91
- # An HTTP parse error has occurred.
92
- # +server+ is the Server object, +env+ the request, and +error+ a
93
- # parsing exception.
94
- #
95
- def parse_error(server, env, error)
96
- @stderr.puts "#{Time.now}: HTTP parse error, malformed request (#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}): #{error.inspect}\n---\n"
97
- end
98
-
99
- # An SSL error has occurred.
100
- # +server+ is the Server object, +peeraddr+ peer address, +peercert+
101
- # any peer certificate (if present), and +error+ an exception object.
102
- #
103
- def ssl_error(server, peeraddr, peercert, error)
104
- subject = peercert ? peercert.subject : nil
105
- @stderr.puts "#{Time.now}: SSL error, peer: #{peeraddr}, peer cert: #{subject}, #{error.inspect}"
33
+ def on_booted(&block)
34
+ register(:on_booted, &block)
106
35
  end
107
36
 
108
- # An unknown error has occurred.
109
- # +server+ is the Server object, +error+ an exception object,
110
- # +kind+ some additional info, and +env+ the request.
111
- #
112
- def unknown_error(server, error, kind="Unknown", env=nil)
113
- if error.respond_to? :render
114
- error.render "#{Time.now}: #{kind} error", @stderr
115
- else
116
- if env
117
- string_block = [ "#{Time.now}: #{kind} error handling request { #{env['REQUEST_METHOD']} #{env['PATH_INFO']} }" ]
118
- string_block << error.inspect
119
- else
120
- string_block = [ "#{Time.now}: #{kind} error: #{error.inspect}" ]
121
- end
122
- string_block << error.backtrace
123
- @stderr.puts string_block.join("\n")
124
- end
37
+ def on_restart(&block)
38
+ register(:on_restart, &block)
125
39
  end
126
40
 
127
- def on_booted(&block)
128
- register(:on_booted, &block)
41
+ def on_stopped(&block)
42
+ register(:on_stopped, &block)
129
43
  end
130
44
 
131
45
  def fire_on_booted!
132
46
  fire(:on_booted)
133
47
  end
134
48
 
135
- DEFAULT = new(STDOUT, STDERR)
136
-
137
- # Returns an Events object which writes its status to 2 StringIO
138
- # objects.
139
- #
140
- def self.strings
141
- Events.new StringIO.new, StringIO.new
142
- end
143
-
144
- def self.stdio
145
- Events.new $stdout, $stderr
49
+ def fire_on_restart!
50
+ fire(:on_restart)
146
51
  end
147
52
 
148
- def self.null
149
- n = NullIO.new
150
- Events.new n, n
53
+ def fire_on_stopped!
54
+ fire(:on_stopped)
151
55
  end
152
56
  end
153
57
  end
@@ -1,9 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/detect'
3
+ require 'stringio'
4
4
 
5
- if Puma.jruby?
6
- require 'puma/java_io_buffer'
7
- else
8
- require 'puma/puma_http11'
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
9
46
  end
@@ -16,69 +16,12 @@ module Puma
16
16
  def self.chdir_exec(dir, argv)
17
17
  chdir(dir)
18
18
  cmd = argv.first
19
- argv = ([:string] * argv.size).zip(argv).flatten
19
+ argv = ([:string] * argv.size).zip(argv)
20
+ argv.flatten!
20
21
  argv << :string
21
22
  argv << nil
22
23
  execlp(cmd, *argv)
23
24
  raise SystemCallError.new(FFI.errno)
24
25
  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
26
  end
84
27
  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