puma 5.3.2 → 6.0.0

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