puma 5.6.4 → 6.3.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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +275 -4
  3. data/LICENSE +0 -0
  4. data/README.md +60 -20
  5. data/bin/puma-wild +1 -1
  6. data/docs/architecture.md +0 -0
  7. data/docs/compile_options.md +34 -0
  8. data/docs/deployment.md +0 -0
  9. data/docs/fork_worker.md +1 -3
  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 +0 -0
  14. data/docs/jungle/rc.d/README.md +0 -0
  15. data/docs/jungle/rc.d/puma.conf +0 -0
  16. data/docs/kubernetes.md +0 -0
  17. data/docs/nginx.md +1 -1
  18. data/docs/plugins.md +0 -0
  19. data/docs/rails_dev_mode.md +0 -0
  20. data/docs/restart.md +0 -0
  21. data/docs/signals.md +0 -0
  22. data/docs/stats.md +0 -0
  23. data/docs/systemd.md +1 -2
  24. data/docs/testing_benchmarks_local_files.md +150 -0
  25. data/docs/testing_test_rackup_ci_files.md +36 -0
  26. data/ext/puma_http11/PumaHttp11Service.java +0 -0
  27. data/ext/puma_http11/ext_help.h +0 -0
  28. data/ext/puma_http11/extconf.rb +18 -10
  29. data/ext/puma_http11/http11_parser.c +1 -1
  30. data/ext/puma_http11/http11_parser.h +1 -1
  31. data/ext/puma_http11/http11_parser.java.rl +2 -2
  32. data/ext/puma_http11/http11_parser.rl +2 -2
  33. data/ext/puma_http11/http11_parser_common.rl +2 -2
  34. data/ext/puma_http11/mini_ssl.c +93 -26
  35. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  36. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  37. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
  38. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +166 -65
  39. data/ext/puma_http11/puma_http11.c +17 -9
  40. data/lib/puma/app/status.rb +7 -4
  41. data/lib/puma/binder.rb +49 -52
  42. data/lib/puma/cli.rb +12 -18
  43. data/lib/puma/client.rb +69 -23
  44. data/lib/puma/cluster/worker.rb +18 -11
  45. data/lib/puma/cluster/worker_handle.rb +4 -1
  46. data/lib/puma/cluster.rb +33 -30
  47. data/lib/puma/commonlogger.rb +21 -14
  48. data/lib/puma/configuration.rb +76 -58
  49. data/lib/puma/const.rb +129 -92
  50. data/lib/puma/control_cli.rb +21 -18
  51. data/lib/puma/detect.rb +4 -0
  52. data/lib/puma/dsl.rb +187 -49
  53. data/lib/puma/error_logger.rb +18 -9
  54. data/lib/puma/events.rb +6 -126
  55. data/lib/puma/io_buffer.rb +39 -4
  56. data/lib/puma/jruby_restart.rb +2 -1
  57. data/lib/puma/json_serialization.rb +0 -0
  58. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  59. data/lib/puma/launcher.rb +113 -175
  60. data/lib/puma/log_writer.rb +147 -0
  61. data/lib/puma/minissl/context_builder.rb +24 -12
  62. data/lib/puma/minissl.rb +108 -15
  63. data/lib/puma/null_io.rb +5 -0
  64. data/lib/puma/plugin/systemd.rb +90 -0
  65. data/lib/puma/plugin/tmp_restart.rb +1 -1
  66. data/lib/puma/plugin.rb +0 -0
  67. data/lib/puma/rack/builder.rb +6 -6
  68. data/lib/puma/rack/urlmap.rb +0 -0
  69. data/lib/puma/rack_default.rb +19 -4
  70. data/lib/puma/reactor.rb +19 -10
  71. data/lib/puma/request.rb +365 -166
  72. data/lib/puma/runner.rb +52 -20
  73. data/lib/puma/sd_notify.rb +149 -0
  74. data/lib/puma/server.rb +73 -73
  75. data/lib/puma/single.rb +13 -11
  76. data/lib/puma/state_file.rb +2 -4
  77. data/lib/puma/thread_pool.rb +23 -19
  78. data/lib/puma/util.rb +12 -14
  79. data/lib/puma.rb +12 -11
  80. data/lib/rack/handler/puma.rb +113 -86
  81. data/tools/Dockerfile +0 -0
  82. data/tools/trickletest.rb +0 -0
  83. metadata +10 -5
  84. data/lib/puma/queue_close.rb +0 -26
  85. data/lib/puma/systemd.rb +0 -46
@@ -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
data/lib/puma/launcher.rb CHANGED
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/events'
4
- require 'puma/detect'
5
- require 'puma/cluster'
6
- require 'puma/single'
7
- require 'puma/const'
8
- require 'puma/binder'
3
+ require_relative 'log_writer'
4
+ require_relative 'events'
5
+ require_relative 'detect'
6
+ require_relative 'cluster'
7
+ require_relative 'single'
8
+ require_relative 'const'
9
+ require_relative 'binder'
9
10
 
10
11
  module Puma
11
12
  # Puma::Launcher is the single entry point for starting a Puma server based on user
@@ -15,18 +16,14 @@ module Puma
15
16
  # It is responsible for either launching a cluster of Puma workers or a single
16
17
  # puma server.
17
18
  class Launcher
18
- # @deprecated 6.0.0
19
- KEYS_NOT_TO_PERSIST_IN_STATE = [
20
- :logger, :lowlevel_error_handler,
21
- :before_worker_shutdown, :before_worker_boot, :before_worker_fork,
22
- :after_worker_boot, :before_fork, :on_restart
23
- ]
19
+ autoload :BundlePruner, 'puma/launcher/bundle_pruner'
20
+
24
21
  # Returns an instance of Launcher
25
22
  #
26
23
  # +conf+ A Puma::Configuration object indicating how to run the server.
27
24
  #
28
25
  # +launcher_args+ A Hash that currently has one required key `:events`,
29
- # this is expected to hold an object similar to an `Puma::Events.stdio`,
26
+ # this is expected to hold an object similar to an `Puma::LogWriter.stdio`,
30
27
  # this object will be responsible for broadcasting Puma's internal state
31
28
  # to a logging destination. An optional key `:argv` can be supplied,
32
29
  # this should be an array of strings, these arguments are re-used when
@@ -40,25 +37,35 @@ module Puma
40
37
  # [200, {}, ["hello world"]]
41
38
  # end
42
39
  # end
43
- # Puma::Launcher.new(conf, events: Puma::Events.stdio).run
40
+ # Puma::Launcher.new(conf, log_writer: Puma::LogWriter.stdio).run
44
41
  def initialize(conf, launcher_args={})
45
42
  @runner = nil
46
- @events = launcher_args[:events] || Events::DEFAULT
43
+ @log_writer = launcher_args[:log_writer] || LogWriter::DEFAULT
44
+ @events = launcher_args[:events] || Events.new
47
45
  @argv = launcher_args[:argv] || []
48
46
  @original_argv = @argv.dup
49
47
  @config = conf
50
48
 
51
- @binder = Binder.new(@events, conf)
52
- @binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
53
- @binder.create_activated_fds(ENV).each { |k| ENV.delete k }
54
-
55
- @environment = conf.environment
49
+ @config.options[:log_writer] = @log_writer
56
50
 
57
51
  # Advertise the Configuration
58
52
  Puma.cli_config = @config if defined?(Puma.cli_config)
59
53
 
60
54
  @config.load
61
55
 
56
+ @binder = Binder.new(@log_writer, conf)
57
+ @binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
58
+ @binder.create_activated_fds(ENV).each { |k| ENV.delete k }
59
+
60
+ @environment = conf.environment
61
+
62
+ # Load the systemd integration if we detect systemd's NOTIFY_SOCKET.
63
+ # Skip this on JRuby though, because it is incompatible with the systemd
64
+ # integration due to https://github.com/jruby/jruby/issues/6504
65
+ if ENV["NOTIFY_SOCKET"] && !Puma.jruby?
66
+ @config.plugins.create('systemd')
67
+ end
68
+
62
69
  if @config.options[:bind_to_activated_sockets]
63
70
  @config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
64
71
  @config.options[:binds],
@@ -69,8 +76,10 @@ module Puma
69
76
  @options = @config.options
70
77
  @config.clamp
71
78
 
72
- @events.formatter = Events::PidFormatter.new if clustered?
73
- @events.formatter = options[:log_formatter] if @options[:log_formatter]
79
+ @log_writer.formatter = LogWriter::PidFormatter.new if clustered?
80
+ @log_writer.formatter = options[:log_formatter] if @options[:log_formatter]
81
+
82
+ @log_writer.custom_logger = options[:custom_logger] if @options[:custom_logger]
74
83
 
75
84
  generate_restart_data
76
85
 
@@ -80,17 +89,17 @@ module Puma
80
89
 
81
90
  Dir.chdir(@restart_dir)
82
91
 
83
- prune_bundler if prune_bundler?
92
+ prune_bundler!
84
93
 
85
94
  @environment = @options[:environment] if @options[:environment]
86
95
  set_rack_environment
87
96
 
88
97
  if clustered?
89
- @options[:logger] = @events
98
+ @options[:logger] = @log_writer
90
99
 
91
- @runner = Cluster.new(self, @events)
100
+ @runner = Cluster.new(self)
92
101
  else
93
- @runner = Single.new(self, @events)
102
+ @runner = Single.new(self)
94
103
  end
95
104
  Puma.stats_object = @runner
96
105
 
@@ -99,7 +108,7 @@ module Puma
99
108
  log_config if ENV['PUMA_LOG_CONFIG']
100
109
  end
101
110
 
102
- attr_reader :binder, :events, :config, :options, :restart_dir
111
+ attr_reader :binder, :log_writer, :events, :config, :options, :restart_dir
103
112
 
104
113
  # Return stats about the server
105
114
  def stats
@@ -115,7 +124,7 @@ module Puma
115
124
  permission = @options[:state_permission]
116
125
  return unless path
117
126
 
118
- require 'puma/state_file'
127
+ require_relative 'state_file'
119
128
 
120
129
  sf = StateFile.new
121
130
  sf.pid = Process.pid
@@ -159,18 +168,20 @@ module Puma
159
168
  true
160
169
  end
161
170
 
171
+ # Begin a refork if supported
172
+ def refork
173
+ if clustered? && @runner.respond_to?(:fork_worker!) && @options[:fork_worker]
174
+ @runner.fork_worker!
175
+ true
176
+ else
177
+ log "* refork called but not available."
178
+ false
179
+ end
180
+ end
181
+
162
182
  # Run the server. This blocks until the server is stopped
163
183
  def run
164
- previous_env =
165
- if defined?(Bundler)
166
- env = Bundler::ORIGINAL_ENV.dup
167
- # add -rbundler/setup so we load from Gemfile when restarting
168
- bundle = "-rbundler/setup"
169
- env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
170
- env
171
- else
172
- ENV.to_h
173
- end
184
+ previous_env = get_env
174
185
 
175
186
  @config.clamp
176
187
 
@@ -178,24 +189,11 @@ module Puma
178
189
 
179
190
  setup_signals
180
191
  set_process_title
181
- integrate_with_systemd
192
+
193
+ # This blocks until the server is stopped
182
194
  @runner.run
183
195
 
184
- case @status
185
- when :halt
186
- log "* Stopping immediately!"
187
- @runner.stop_control
188
- when :run, :stop
189
- graceful_stop
190
- when :restart
191
- log "* Restarting..."
192
- ENV.replace(previous_env)
193
- @runner.stop_control
194
- restart!
195
- when :exit
196
- # nothing
197
- end
198
- close_binder_listeners unless @status == :restart
196
+ do_run_finished(previous_env)
199
197
  end
200
198
 
201
199
  # Return all tcp ports the launcher may be using, TCP or SSL
@@ -239,30 +237,56 @@ module Puma
239
237
 
240
238
  private
241
239
 
242
- # If configured, write the pid of the current process out
243
- # to a file.
244
- def write_pid
245
- path = @options[:pidfile]
246
- return unless path
247
- cur_pid = Process.pid
248
- File.write path, cur_pid, mode: 'wb:UTF-8'
249
- at_exit do
250
- delete_pidfile if cur_pid == Process.pid
240
+ def get_env
241
+ if defined?(Bundler)
242
+ env = Bundler::ORIGINAL_ENV.dup
243
+ # add -rbundler/setup so we load from Gemfile when restarting
244
+ bundle = "-rbundler/setup"
245
+ env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
246
+ env
247
+ else
248
+ ENV.to_h
251
249
  end
252
250
  end
253
251
 
254
- def reload_worker_directory
255
- @runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
252
+ def do_run_finished(previous_env)
253
+ case @status
254
+ when :halt
255
+ do_forceful_stop
256
+ when :run, :stop
257
+ do_graceful_stop
258
+ when :restart
259
+ do_restart(previous_env)
260
+ end
261
+
262
+ close_binder_listeners unless @status == :restart
263
+ end
264
+
265
+ def do_forceful_stop
266
+ log "* Stopping immediately!"
267
+ @runner.stop_control
268
+ end
269
+
270
+ def do_graceful_stop
271
+ @events.fire_on_stopped!
272
+ @runner.stop_blocked
273
+ end
274
+
275
+ def do_restart(previous_env)
276
+ log "* Restarting..."
277
+ ENV.replace(previous_env)
278
+ @runner.stop_control
279
+ restart!
256
280
  end
257
281
 
258
282
  def restart!
259
283
  @events.fire_on_restart!
260
- @config.run_hooks :on_restart, self, @events
284
+ @config.run_hooks :on_restart, self, @log_writer
261
285
 
262
286
  if Puma.jruby?
263
287
  close_binder_listeners
264
288
 
265
- require 'puma/jruby_restart'
289
+ require_relative 'jruby_restart'
266
290
  JRubyRestart.chdir_exec(@restart_dir, restart_args)
267
291
  elsif Puma.windows?
268
292
  close_binder_listeners
@@ -279,94 +303,24 @@ module Puma
279
303
  end
280
304
  end
281
305
 
282
- # @!attribute [r] files_to_require_after_prune
283
- def files_to_require_after_prune
284
- puma = spec_for_gem("puma")
285
-
286
- require_paths_for_gem(puma) + extra_runtime_deps_directories
287
- end
288
-
289
- # @!attribute [r] extra_runtime_deps_directories
290
- def extra_runtime_deps_directories
291
- Array(@options[:extra_runtime_dependencies]).map do |d_name|
292
- if (spec = spec_for_gem(d_name))
293
- require_paths_for_gem(spec)
294
- else
295
- log "* Could not load extra dependency: #{d_name}"
296
- nil
297
- end
298
- end.flatten.compact
299
- end
300
-
301
- # @!attribute [r] puma_wild_location
302
- def puma_wild_location
303
- puma = spec_for_gem("puma")
304
- dirs = require_paths_for_gem(puma)
305
- puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
306
- File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
307
- end
308
-
309
- def prune_bundler
310
- return if ENV['PUMA_BUNDLER_PRUNED']
311
- return unless defined?(Bundler)
312
- require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
313
- unless puma_wild_location
314
- log "! Unable to prune Bundler environment, continuing"
315
- return
316
- end
317
-
318
- dirs = files_to_require_after_prune
319
-
320
- log '* Pruning Bundler environment'
321
- home = ENV['GEM_HOME']
322
- bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
323
- bundle_app_config = Bundler.original_env['BUNDLE_APP_CONFIG']
324
- with_unbundled_env do
325
- ENV['GEM_HOME'] = home
326
- ENV['BUNDLE_GEMFILE'] = bundle_gemfile
327
- ENV['PUMA_BUNDLER_PRUNED'] = '1'
328
- ENV["BUNDLE_APP_CONFIG"] = bundle_app_config
329
- args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':')] + @original_argv
330
- # Ruby 2.0+ defaults to true which breaks socket activation
331
- args += [{:close_others => false}]
332
- Kernel.exec(*args)
333
- end
334
- end
335
-
336
- #
337
- # Puma's systemd integration allows Puma to inform systemd:
338
- # 1. when it has successfully started
339
- # 2. when it is starting shutdown
340
- # 3. periodically for a liveness check with a watchdog thread
341
- #
342
-
343
- def integrate_with_systemd
344
- return unless ENV["NOTIFY_SOCKET"]
345
-
346
- begin
347
- require 'puma/systemd'
348
- rescue LoadError
349
- log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
350
- return
306
+ # If configured, write the pid of the current process out
307
+ # to a file.
308
+ def write_pid
309
+ path = @options[:pidfile]
310
+ return unless path
311
+ cur_pid = Process.pid
312
+ File.write path, cur_pid, mode: 'wb:UTF-8'
313
+ at_exit do
314
+ delete_pidfile if cur_pid == Process.pid
351
315
  end
352
-
353
- log "* Enabling systemd notification integration"
354
-
355
- systemd = Systemd.new(@events)
356
- systemd.hook_events
357
- systemd.start_watchdog
358
316
  end
359
317
 
360
- def spec_for_gem(gem_name)
361
- Bundler.rubygems.loaded_specs(gem_name)
362
- end
363
-
364
- def require_paths_for_gem(gem_spec)
365
- gem_spec.full_require_paths
318
+ def reload_worker_directory
319
+ @runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
366
320
  end
367
321
 
368
322
  def log(str)
369
- @events.log str
323
+ @log_writer.log(str)
370
324
  end
371
325
 
372
326
  def clustered?
@@ -374,15 +328,10 @@ module Puma
374
328
  end
375
329
 
376
330
  def unsupported(str)
377
- @events.error(str)
331
+ @log_writer.error(str)
378
332
  raise UnsupportedOption
379
333
  end
380
334
 
381
- def graceful_stop
382
- @events.fire_on_stopped!
383
- @runner.stop_blocked
384
- end
385
-
386
335
  def set_process_title
387
336
  Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
388
337
  end
@@ -408,6 +357,11 @@ module Puma
408
357
  @options[:prune_bundler] && clustered? && !@options[:preload_app]
409
358
  end
410
359
 
360
+ def prune_bundler!
361
+ return unless prune_bundler?
362
+ BundlePruner.new(@original_argv, @options[:extra_runtime_dependencies], @log_writer).prune
363
+ end
364
+
411
365
  def generate_restart_data
412
366
  if dir = @options[:directory]
413
367
  @restart_dir = dir
@@ -474,7 +428,8 @@ module Puma
474
428
 
475
429
  begin
476
430
  Signal.trap "SIGTERM" do
477
- graceful_stop
431
+ # Shortcut the control flow in case raise_exception_on_sigterm is true
432
+ do_graceful_stop
478
433
 
479
434
  raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
480
435
  end
@@ -506,8 +461,8 @@ module Puma
506
461
  unless Puma.jruby? # INFO in use by JVM already
507
462
  Signal.trap "SIGINFO" do
508
463
  thread_status do |name, backtrace|
509
- @events.log name
510
- @events.log backtrace.map { |bt| " #{bt}" }
464
+ @log_writer.log(name)
465
+ @log_writer.log(backtrace.map { |bt| " #{bt}" })
511
466
  end
512
467
  end
513
468
  end
@@ -517,23 +472,6 @@ module Puma
517
472
  end
518
473
  end
519
474
 
520
- def require_rubygems_min_version!(min_version, feature)
521
- return if min_version <= Gem::Version.new(Gem::VERSION)
522
-
523
- raise "#{feature} is not supported on your version of RubyGems. " \
524
- "You must have RubyGems #{min_version}+ to use this feature."
525
- end
526
-
527
- # @version 5.0.0
528
- def with_unbundled_env
529
- bundler_ver = Gem::Version.new(Bundler::VERSION)
530
- if bundler_ver < Gem::Version.new('2.1.0')
531
- Bundler.with_clean_env { yield }
532
- else
533
- Bundler.with_unbundled_env { yield }
534
- end
535
- end
536
-
537
475
  def log_config
538
476
  log "Configuration:"
539
477
 
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'null_io'
4
+ require_relative 'error_logger'
5
+ require 'stringio'
6
+ require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
7
+
8
+ module Puma
9
+
10
+ # Handles logging concerns for both standard messages
11
+ # (+stdout+) and errors (+stderr+).
12
+ class LogWriter
13
+
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
+ LOG_QUEUE = Queue.new
27
+
28
+ attr_reader :stdout,
29
+ :stderr
30
+
31
+ attr_accessor :formatter, :custom_logger
32
+
33
+ # Create a LogWriter that prints to +stdout+ and +stderr+.
34
+ def initialize(stdout, stderr)
35
+ @formatter = DefaultFormatter.new
36
+ @custom_logger = nil
37
+ @stdout = stdout
38
+ @stderr = stderr
39
+
40
+ @debug = ENV.key?('PUMA_DEBUG')
41
+ @error_logger = ErrorLogger.new(@stderr)
42
+ end
43
+
44
+ DEFAULT = new(STDOUT, STDERR)
45
+
46
+ # Returns an LogWriter object which writes its status to
47
+ # two StringIO objects.
48
+ def self.strings
49
+ LogWriter.new(StringIO.new, StringIO.new)
50
+ end
51
+
52
+ def self.stdio
53
+ LogWriter.new($stdout, $stderr)
54
+ end
55
+
56
+ def self.null
57
+ n = NullIO.new
58
+ LogWriter.new(n, n)
59
+ end
60
+
61
+ # Write +str+ to +@stdout+
62
+ def log(str)
63
+ if @custom_logger&.respond_to?(:write)
64
+ @custom_logger.write(format(str))
65
+ else
66
+ internal_write "#{@formatter.call str}\n"
67
+ end
68
+ end
69
+
70
+ def write(str)
71
+ internal_write @formatter.call(str)
72
+ end
73
+
74
+ def internal_write(str)
75
+ LOG_QUEUE << str
76
+ while (w_str = LOG_QUEUE.pop(true)) do
77
+ begin
78
+ @stdout.is_a?(IO) and @stdout.wait_writable(1)
79
+ @stdout.write w_str
80
+ @stdout.flush unless @stdout.sync
81
+ rescue Errno::EPIPE, Errno::EBADF, IOError, Errno::EINVAL
82
+ # 'Invalid argument' (Errno::EINVAL) may be raised by flush
83
+ end
84
+ end
85
+ rescue ThreadError
86
+ end
87
+ private :internal_write
88
+
89
+ def debug?
90
+ @debug
91
+ end
92
+
93
+ def debug(str)
94
+ log("% #{str}") if @debug
95
+ end
96
+
97
+ # Write +str+ to +@stderr+
98
+ def error(str)
99
+ @error_logger.info(text: @formatter.call("ERROR: #{str}"))
100
+ exit 1
101
+ end
102
+
103
+ def format(str)
104
+ formatter.call(str)
105
+ end
106
+
107
+ # An HTTP connection error has occurred.
108
+ # +error+ a connection exception, +req+ the request,
109
+ # and +text+ additional info
110
+ # @version 5.0.0
111
+ def connection_error(error, req, text="HTTP connection error")
112
+ @error_logger.info(error: error, req: req, text: text)
113
+ end
114
+
115
+ # An HTTP parse error has occurred.
116
+ # +error+ a parsing exception,
117
+ # and +req+ the request.
118
+ def parse_error(error, req)
119
+ @error_logger.info(error: error, req: req, text: 'HTTP parse error, malformed request')
120
+ end
121
+
122
+ # An SSL error has occurred.
123
+ # @param error <Puma::MiniSSL::SSLError>
124
+ # @param ssl_socket <Puma::MiniSSL::Socket>
125
+ def ssl_error(error, ssl_socket)
126
+ peeraddr = ssl_socket.peeraddr.last rescue "<unknown>"
127
+ peercert = ssl_socket.peercert
128
+ subject = peercert&.subject
129
+ @error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
130
+ end
131
+
132
+ # An unknown error has occurred.
133
+ # +error+ an exception object, +req+ the request,
134
+ # and +text+ additional info
135
+ def unknown_error(error, req=nil, text="Unknown error")
136
+ @error_logger.info(error: error, req: req, text: text)
137
+ end
138
+
139
+ # Log occurred error debug dump.
140
+ # +error+ an exception object, +req+ the request,
141
+ # and +text+ additional info
142
+ # @version 5.0.0
143
+ def debug_error(error, req=nil, text="")
144
+ @error_logger.debug(error: error, req: req, text: text)
145
+ end
146
+ end
147
+ end