puma 5.6.4 → 6.1.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +199 -3
  3. data/README.md +22 -17
  4. data/bin/puma-wild +1 -1
  5. data/docs/compile_options.md +34 -0
  6. data/docs/fork_worker.md +1 -3
  7. data/docs/nginx.md +1 -1
  8. data/docs/systemd.md +1 -2
  9. data/docs/testing_benchmarks_local_files.md +150 -0
  10. data/docs/testing_test_rackup_ci_files.md +36 -0
  11. data/ext/puma_http11/extconf.rb +18 -10
  12. data/ext/puma_http11/http11_parser.c +1 -1
  13. data/ext/puma_http11/http11_parser.h +1 -1
  14. data/ext/puma_http11/http11_parser.java.rl +2 -2
  15. data/ext/puma_http11/http11_parser.rl +2 -2
  16. data/ext/puma_http11/http11_parser_common.rl +2 -2
  17. data/ext/puma_http11/mini_ssl.c +63 -24
  18. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  19. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
  20. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +166 -65
  21. data/ext/puma_http11/puma_http11.c +17 -9
  22. data/lib/puma/app/status.rb +6 -3
  23. data/lib/puma/binder.rb +41 -46
  24. data/lib/puma/cli.rb +11 -17
  25. data/lib/puma/client.rb +54 -16
  26. data/lib/puma/cluster/worker.rb +18 -11
  27. data/lib/puma/cluster/worker_handle.rb +4 -1
  28. data/lib/puma/cluster.rb +33 -30
  29. data/lib/puma/configuration.rb +75 -58
  30. data/lib/puma/const.rb +76 -88
  31. data/lib/puma/control_cli.rb +21 -18
  32. data/lib/puma/detect.rb +4 -0
  33. data/lib/puma/dsl.rb +111 -49
  34. data/lib/puma/error_logger.rb +17 -9
  35. data/lib/puma/events.rb +6 -126
  36. data/lib/puma/io_buffer.rb +39 -4
  37. data/lib/puma/jruby_restart.rb +2 -1
  38. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  39. data/lib/puma/launcher.rb +111 -175
  40. data/lib/puma/log_writer.rb +141 -0
  41. data/lib/puma/minissl/context_builder.rb +23 -12
  42. data/lib/puma/minissl.rb +91 -15
  43. data/lib/puma/null_io.rb +5 -0
  44. data/lib/puma/plugin/systemd.rb +90 -0
  45. data/lib/puma/plugin/tmp_restart.rb +1 -1
  46. data/lib/puma/rack/builder.rb +4 -4
  47. data/lib/puma/rack_default.rb +19 -4
  48. data/lib/puma/reactor.rb +4 -4
  49. data/lib/puma/request.rb +344 -161
  50. data/lib/puma/runner.rb +52 -20
  51. data/lib/puma/sd_notify.rb +149 -0
  52. data/lib/puma/server.rb +57 -69
  53. data/lib/puma/single.rb +13 -11
  54. data/lib/puma/state_file.rb +2 -4
  55. data/lib/puma/thread_pool.rb +16 -16
  56. data/lib/puma/util.rb +12 -14
  57. data/lib/puma.rb +12 -11
  58. data/lib/rack/handler/puma.rb +115 -94
  59. metadata +10 -5
  60. data/lib/puma/queue_close.rb +0 -26
  61. data/lib/puma/systemd.rb +0 -46
@@ -17,26 +17,27 @@ module Puma
17
17
  CMD_PATH_SIG_MAP = {
18
18
  'gc' => nil,
19
19
  'gc-stats' => nil,
20
- 'halt' => 'SIGQUIT',
21
- 'phased-restart' => 'SIGUSR1',
22
- 'refork' => 'SIGURG',
20
+ 'halt' => 'SIGQUIT',
21
+ 'info' => 'SIGINFO',
22
+ 'phased-restart' => 'SIGUSR1',
23
+ 'refork' => 'SIGURG',
23
24
  'reload-worker-directory' => nil,
24
- 'restart' => 'SIGUSR2',
25
+ 'reopen-log' => 'SIGHUP',
26
+ 'restart' => 'SIGUSR2',
25
27
  'start' => nil,
26
28
  'stats' => nil,
27
29
  'status' => '',
28
- 'stop' => 'SIGTERM',
29
- 'thread-backtraces' => nil
30
+ 'stop' => 'SIGTERM',
31
+ 'thread-backtraces' => nil,
32
+ 'worker-count-down' => 'SIGTTOU',
33
+ 'worker-count-up' => 'SIGTTIN'
30
34
  }.freeze
31
35
 
32
- # @deprecated 6.0.0
33
- COMMANDS = CMD_PATH_SIG_MAP.keys.freeze
34
-
35
36
  # commands that cannot be used in a request
36
- NO_REQ_COMMANDS = %w{refork}.freeze
37
+ NO_REQ_COMMANDS = %w[info reopen-log worker-count-down worker-count-up].freeze
37
38
 
38
39
  # @version 5.0.0
39
- PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}.freeze
40
+ PRINTABLE_COMMANDS = %w[gc-stats stats thread-backtraces].freeze
40
41
 
41
42
  def initialize(argv, stdout=STDOUT, stderr=STDERR)
42
43
  @state = nil
@@ -185,8 +186,6 @@ module Puma
185
186
 
186
187
  if @command == 'status'
187
188
  message 'Puma is started'
188
- elsif NO_REQ_COMMANDS.include? @command
189
- raise "Invalid request command: #{@command}"
190
189
  else
191
190
  url = "/#{@command}"
192
191
 
@@ -242,7 +241,11 @@ module Puma
242
241
  @stdout.flush unless @stdout.sync
243
242
  return
244
243
  elsif sig.start_with? 'SIG'
245
- Process.kill sig, @pid
244
+ if Signal.list.key? sig.sub(/\ASIG/, '')
245
+ Process.kill sig, @pid
246
+ else
247
+ raise "Signal '#{sig}' not available'"
248
+ end
246
249
  elsif @command == 'status'
247
250
  begin
248
251
  Process.kill 0, @pid
@@ -268,7 +271,7 @@ module Puma
268
271
  return start if @command == 'start'
269
272
  prepare_configuration
270
273
 
271
- if Puma.windows? || @control_url
274
+ if Puma.windows? || @control_url && !NO_REQ_COMMANDS.include?(@command)
272
275
  send_request
273
276
  else
274
277
  send_signal
@@ -281,7 +284,7 @@ module Puma
281
284
 
282
285
  private
283
286
  def start
284
- require 'puma/cli'
287
+ require_relative 'cli'
285
288
 
286
289
  run_args = []
287
290
 
@@ -293,13 +296,13 @@ module Puma
293
296
  run_args += ["-C", @config_file] if @config_file
294
297
  run_args += ["-e", @environment] if @environment
295
298
 
296
- events = Puma::Events.new @stdout, @stderr
299
+ log_writer = Puma::LogWriter.new(@stdout, @stderr)
297
300
 
298
301
  # replace $0 because puma use it to generate restart command
299
302
  puma_cmd = $0.gsub(/pumactl$/, 'puma')
300
303
  $0 = puma_cmd if File.exist?(puma_cmd)
301
304
 
302
- cli = Puma::CLI.new run_args, events
305
+ cli = Puma::CLI.new run_args, log_writer
303
306
  cli.run
304
307
  end
305
308
  end
data/lib/puma/detect.rb CHANGED
@@ -8,6 +8,8 @@ module Puma
8
8
  # @version 5.2.1
9
9
  HAS_FORK = ::Process.respond_to? :fork
10
10
 
11
+ HAS_NATIVE_IO_WAIT = ::IO.public_instance_methods(false).include? :wait_readable
12
+
11
13
  IS_JRUBY = Object.const_defined? :JRUBY_VERSION
12
14
 
13
15
  IS_OSX = RUBY_PLATFORM.include? 'darwin'
@@ -15,6 +17,8 @@ module Puma
15
17
  IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/) ||
16
18
  IS_JRUBY && RUBY_DESCRIPTION.include?('mswin')
17
19
 
20
+ IS_LINUX = !(IS_OSX || IS_WINDOWS)
21
+
18
22
  # @version 5.2.0
19
23
  IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
20
24
 
data/lib/puma/dsl.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/const'
3
+ require_relative 'const'
4
+ require_relative 'util'
4
5
 
5
6
  module Puma
6
7
  # The methods that are available for use inside the configuration file.
@@ -31,8 +32,24 @@ module Puma
31
32
  # You can also find many examples being used by the test suite in
32
33
  # +test/config+.
33
34
  #
35
+ # Puma v6 adds the option to specify a key name (String or Symbol) to the
36
+ # hooks that run inside the forked workers. All the hooks run inside the
37
+ # {Puma::Cluster::Worker#run} method.
38
+ #
39
+ # Previously, the worker index and the LogWriter instance were passed to the
40
+ # hook blocks/procs. If a key name is specified, a hash is passed as the last
41
+ # parameter. This allows storage of data, typically objects that are created
42
+ # before the worker that need to be passed to the hook when the worker is shutdown.
43
+ #
44
+ # The following hooks have been updated:
45
+ #
46
+ # | DSL Method | Options Key | Fork Block Location |
47
+ # | on_worker_boot | :before_worker_boot | inside, before |
48
+ # | on_worker_shutdown | :before_worker_shutdown | inside, after |
49
+ # | on_refork | :before_refork | inside |
50
+ #
34
51
  class DSL
35
- include ConfigDefault
52
+ ON_WORKER_KEY = [String, Symbol].freeze
36
53
 
37
54
  # convenience method so logic can be used in CI
38
55
  # @see ssl_bind
@@ -46,27 +63,59 @@ module Puma
46
63
  else ''
47
64
  end
48
65
 
49
- ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify)
66
+ ca_additions = "&ca=#{Puma::Util.escape(opts[:ca])}" if ['peer', 'force_peer'].include?(verify)
50
67
 
68
+ low_latency_str = opts.key?(:low_latency) ? "&low_latency=#{opts[:low_latency]}" : ''
51
69
  backlog_str = opts[:backlog] ? "&backlog=#{Integer(opts[:backlog])}" : ''
52
70
 
53
71
  if defined?(JRUBY_VERSION)
54
- ssl_cipher_list = opts[:ssl_cipher_list] ?
55
- "&ssl_cipher_list=#{opts[:ssl_cipher_list]}" : nil
72
+ cipher_suites = opts[:ssl_cipher_list] ? "&ssl_cipher_list=#{opts[:ssl_cipher_list]}" : nil # old name
73
+ cipher_suites = "#{cipher_suites}&cipher_suites=#{opts[:cipher_suites]}" if opts[:cipher_suites]
74
+ protocols = opts[:protocols] ? "&protocols=#{opts[:protocols]}" : nil
56
75
 
57
76
  keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
77
+ keystore_additions = "#{keystore_additions}&keystore-type=#{opts[:keystore_type]}" if opts[:keystore_type]
78
+ if opts[:truststore]
79
+ truststore_additions = "&truststore=#{opts[:truststore]}"
80
+ truststore_additions = "#{truststore_additions}&truststore-pass=#{opts[:truststore_pass]}" if opts[:truststore_pass]
81
+ truststore_additions = "#{truststore_additions}&truststore-type=#{opts[:truststore_type]}" if opts[:truststore_type]
82
+ end
58
83
 
59
- "ssl://#{host}:#{port}?#{keystore_additions}#{ssl_cipher_list}" \
84
+ "ssl://#{host}:#{port}?#{keystore_additions}#{truststore_additions}#{cipher_suites}#{protocols}" \
60
85
  "&verify_mode=#{verify}#{tls_str}#{ca_additions}#{backlog_str}"
61
86
  else
62
- ssl_cipher_filter = opts[:ssl_cipher_filter] ?
63
- "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" : nil
64
-
65
- v_flags = (ary = opts[:verification_flags]) ?
66
- "&verification_flags=#{Array(ary).join ','}" : nil
87
+ ssl_cipher_filter = opts[:ssl_cipher_filter] ? "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" : nil
88
+ v_flags = (ary = opts[:verification_flags]) ? "&verification_flags=#{Array(ary).join ','}" : nil
89
+
90
+ cert_flags = (cert = opts[:cert]) ? "cert=#{Puma::Util.escape(cert)}" : nil
91
+ key_flags = (key = opts[:key]) ? "&key=#{Puma::Util.escape(key)}" : nil
92
+
93
+ reuse_flag =
94
+ if (reuse = opts[:reuse])
95
+ if reuse == true
96
+ '&reuse=dflt'
97
+ elsif reuse.is_a?(Hash) && (reuse.key?(:size) || reuse.key?(:timeout))
98
+ val = +''
99
+ if (size = reuse[:size]) && Integer === size
100
+ val << size.to_s
101
+ end
102
+ if (timeout = reuse[:timeout]) && Integer === timeout
103
+ val << ",#{timeout}"
104
+ end
105
+ if val.empty?
106
+ nil
107
+ else
108
+ "&reuse=#{val}"
109
+ end
110
+ else
111
+ nil
112
+ end
113
+ else
114
+ nil
115
+ end
67
116
 
68
- "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}" \
69
- "#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}"
117
+ "ssl://#{host}:#{port}?#{cert_flags}#{key_flags}#{ssl_cipher_filter}" \
118
+ "#{reuse_flag}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}#{low_latency_str}"
70
119
  end
71
120
  end
72
121
 
@@ -102,7 +151,7 @@ module Puma
102
151
  end
103
152
 
104
153
  def default_host
105
- @options[:default_host] || Configuration::DefaultTCPHost
154
+ @options[:default_host] || Configuration::DEFAULTS[:tcp_host]
106
155
  end
107
156
 
108
157
  def inject(&blk)
@@ -202,6 +251,7 @@ module Puma
202
251
  #
203
252
  # * Set the socket backlog depth with +backlog+, default is 1024.
204
253
  # * Set up an SSL certificate with +key+ & +cert+.
254
+ # * Set up an SSL certificate for mTLS with +key+, +cert+, +ca+ and +verify_mode+.
205
255
  # * Set whether to optimize for low latency instead of throughput with
206
256
  # +low_latency+, default is to not optimize for low latency. This is done
207
257
  # via +Socket::TCP_NODELAY+.
@@ -211,6 +261,8 @@ module Puma
211
261
  # bind 'unix:///var/run/puma.sock?backlog=512'
212
262
  # @example SSL cert
213
263
  # bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem'
264
+ # @example SSL cert for mutual TLS (mTLS)
265
+ # bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem&ca=ca.pem&verify_mode=force_peer'
214
266
  # @example Disable optimization for low latency
215
267
  # bind 'tcp://0.0.0.0:9292?low_latency=false'
216
268
  # @example Socket permissions
@@ -448,6 +500,10 @@ module Puma
448
500
  # Puma will assume you are using the +localhost+ gem and try to load the
449
501
  # appropriate files.
450
502
  #
503
+ # When using the options hash parameter, the `reuse:` value is either
504
+ # `true`, which sets reuse 'on' with default values, or a hash, with `:size`
505
+ # and/or `:timeout` keys, each with integer values.
506
+ #
451
507
  # @example
452
508
  # ssl_bind '127.0.0.1', '9292', {
453
509
  # cert: path_to_cert,
@@ -455,6 +511,7 @@ module Puma
455
511
  # ssl_cipher_filter: cipher_filter, # optional
456
512
  # verify_mode: verify_mode, # default 'none'
457
513
  # verification_flags: flags, # optional, not supported by JRuby
514
+ # reuse: true # optional
458
515
  # }
459
516
  #
460
517
  # @example Using self-signed certificate with the +localhost+ gem:
@@ -464,6 +521,7 @@ module Puma
464
521
  # ssl_bind '127.0.0.1', '9292', {
465
522
  # cert_pem: File.read(path_to_cert),
466
523
  # key_pem: File.read(path_to_key),
524
+ # reuse: {size: 2_000, timeout: 20} # optional
467
525
  # }
468
526
  #
469
527
  # @example For JRuby, two keys are required: +keystore+ & +keystore_pass+
@@ -556,9 +614,8 @@ module Puma
556
614
  # on_worker_boot do
557
615
  # puts 'Before worker boot...'
558
616
  # end
559
- def on_worker_boot(&block)
560
- @options[:before_worker_boot] ||= []
561
- @options[:before_worker_boot] << block
617
+ def on_worker_boot(key = nil, &block)
618
+ process_hook :before_worker_boot, key, block, 'on_worker_boot'
562
619
  end
563
620
 
564
621
  # Code to run immediately before a worker shuts
@@ -573,9 +630,8 @@ module Puma
573
630
  # on_worker_shutdown do
574
631
  # puts 'On worker shutdown...'
575
632
  # end
576
- def on_worker_shutdown(&block)
577
- @options[:before_worker_shutdown] ||= []
578
- @options[:before_worker_shutdown] << block
633
+ def on_worker_shutdown(key = nil, &block)
634
+ process_hook :before_worker_shutdown, key, block, 'on_worker_shutdown'
579
635
  end
580
636
 
581
637
  # Code to run in the master right before a worker is started. The worker's
@@ -589,8 +645,7 @@ module Puma
589
645
  # puts 'Before worker fork...'
590
646
  # end
591
647
  def on_worker_fork(&block)
592
- @options[:before_worker_fork] ||= []
593
- @options[:before_worker_fork] << block
648
+ process_hook :before_worker_fork, nil, block, 'on_worker_fork'
594
649
  end
595
650
 
596
651
  # Code to run in the master after a worker has been started. The worker's
@@ -604,8 +659,7 @@ module Puma
604
659
  # puts 'After worker fork...'
605
660
  # end
606
661
  def after_worker_fork(&block)
607
- @options[:after_worker_fork] ||= []
608
- @options[:after_worker_fork] << block
662
+ process_hook :after_worker_fork, nil, block, 'after_worker_fork'
609
663
  end
610
664
 
611
665
  alias_method :after_worker_boot, :after_worker_fork
@@ -628,9 +682,8 @@ module Puma
628
682
  # end
629
683
  # @version 5.0.0
630
684
  #
631
- def on_refork(&block)
632
- @options[:before_refork] ||= []
633
- @options[:before_refork] << block
685
+ def on_refork(key = nil, &block)
686
+ process_hook :before_refork, key, block, 'on_refork'
634
687
  end
635
688
 
636
689
  # Code to run out-of-band when the worker is idle.
@@ -643,8 +696,7 @@ module Puma
643
696
  #
644
697
  # This can be called multiple times to add several hooks.
645
698
  def out_of_band(&block)
646
- @options[:out_of_band] ||= []
647
- @options[:out_of_band] << block
699
+ process_hook :out_of_band, nil, block, 'out_of_band'
648
700
  end
649
701
 
650
702
  # The directory to operate out of.
@@ -774,7 +826,7 @@ module Puma
774
826
  #
775
827
  def worker_timeout(timeout)
776
828
  timeout = Integer(timeout)
777
- min = @options.fetch(:worker_check_interval, Puma::ConfigDefault::DefaultWorkerCheckInterval)
829
+ min = @options.fetch(:worker_check_interval, Configuration::DEFAULTS[:worker_check_interval])
778
830
 
779
831
  if timeout <= min
780
832
  raise "The minimum worker_timeout must be greater than the worker reporting interval (#{min})"
@@ -878,13 +930,16 @@ module Puma
878
930
  # There are 5 possible values:
879
931
  #
880
932
  # 1. **:socket** (the default) - read the peername from the socket using the
881
- # syscall. This is the normal behavior.
933
+ # syscall. This is the normal behavior. If this fails for any reason (e.g.,
934
+ # if the peer disconnects between the connection being accepted and the getpeername
935
+ # system call), Puma will return "0.0.0.0"
882
936
  # 2. **:localhost** - set the remote address to "127.0.0.1"
883
937
  # 3. **header: <http_header>**- set the remote address to the value of the
884
938
  # provided http header. For instance:
885
939
  # `set_remote_address header: "X-Real-IP"`.
886
940
  # Only the first word (as separated by spaces or comma) is used, allowing
887
- # headers such as X-Forwarded-For to be used as well.
941
+ # headers such as X-Forwarded-For to be used as well. If this header is absent,
942
+ # Puma will fall back to the behavior of :socket
888
943
  # 4. **proxy_protocol: :v1**- set the remote address to the value read from the
889
944
  # HAproxy PROXY protocol, version 1. If the request does not have the PROXY
890
945
  # protocol attached to it, will fall back to :socket
@@ -938,23 +993,6 @@ module Puma
938
993
  @options[:fork_worker] = Integer(after_requests)
939
994
  end
940
995
 
941
- # When enabled, Puma will GC 4 times before forking workers.
942
- # If available (Ruby 2.7+), we will also call GC.compact.
943
- # Not recommended for non-MRI Rubies.
944
- #
945
- # Based on the work of Koichi Sasada and Aaron Patterson, this option may
946
- # decrease memory utilization of preload-enabled cluster-mode Pumas. It will
947
- # also increase time to boot and fork. See your logs for details on how much
948
- # time this adds to your boot process. For most apps, it will be less than one
949
- # second.
950
- #
951
- # @see Puma::Cluster#nakayoshi_gc
952
- # @version 5.0.0
953
- #
954
- def nakayoshi_fork(enabled=true)
955
- @options[:nakayoshi_fork] = enabled
956
- end
957
-
958
996
  # The number of requests to attempt inline before sending a client back to
959
997
  # the reactor to be subject to normal ordering.
960
998
  #
@@ -985,6 +1023,19 @@ module Puma
985
1023
  @options[:mutate_stdout_and_stderr_to_sync_on_write] = enabled
986
1024
  end
987
1025
 
1026
+ # Specify how big the request payload should be, in bytes.
1027
+ # This limit is compared against Content-Length HTTP header.
1028
+ # If the payload size (CONTENT_LENGTH) is larger than http_content_length_limit,
1029
+ # HTTP 413 status code is returned.
1030
+ #
1031
+ # When no Content-Length http header is present, it is compared against the
1032
+ # size of the body of the request.
1033
+ #
1034
+ # The default value for http_content_length_limit is nil.
1035
+ def http_content_length_limit(limit)
1036
+ @options[:http_content_length_limit] = limit
1037
+ end
1038
+
988
1039
  private
989
1040
 
990
1041
  # To avoid adding cert_pem and key_pem as URI params, we store them on the
@@ -1004,5 +1055,16 @@ module Puma
1004
1055
  end
1005
1056
  end
1006
1057
  end
1058
+
1059
+ def process_hook(options_key, key, block, meth)
1060
+ @options[options_key] ||= []
1061
+ if ON_WORKER_KEY.include? key.class
1062
+ @options[options_key] << [block, key.to_sym]
1063
+ elsif key.nil?
1064
+ @options[options_key] << block
1065
+ else
1066
+ raise "'#{method}' key must be String or Symbol"
1067
+ end
1068
+ end
1007
1069
  end
1008
1070
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/const'
3
+ require_relative 'const'
4
4
 
5
5
  module Puma
6
6
  # The implementation of a detailed error logging.
@@ -13,6 +13,8 @@ module Puma
13
13
 
14
14
  REQUEST_FORMAT = %{"%s %s%s" - (%s)}
15
15
 
16
+ LOG_QUEUE = Queue.new
17
+
16
18
  def initialize(ioerr)
17
19
  @ioerr = ioerr
18
20
 
@@ -31,7 +33,7 @@ module Puma
31
33
  # and before all remaining info.
32
34
  #
33
35
  def info(options={})
34
- log title(options)
36
+ internal_write title(options)
35
37
  end
36
38
 
37
39
  # Print occurred error details only if
@@ -53,7 +55,7 @@ module Puma
53
55
  string_block << request_dump(req) if request_parsed?(req)
54
56
  string_block << error.backtrace if error
55
57
 
56
- log string_block.join("\n")
58
+ internal_write string_block.join("\n")
57
59
  end
58
60
 
59
61
  def title(options={})
@@ -93,12 +95,18 @@ module Puma
93
95
  req && req.env[REQUEST_METHOD]
94
96
  end
95
97
 
96
- private
97
-
98
- def log(str)
99
- ioerr.puts str
100
-
101
- ioerr.flush unless ioerr.sync
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
102
109
  end
110
+ private :internal_write
103
111
  end
104
112
  end
data/lib/puma/events.rb CHANGED
@@ -1,52 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "puma/null_io"
4
- require 'puma/error_logger'
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
- # 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
- @debug = ENV.key? 'PUMA_DEBUG'
34
- @error_logger = ErrorLogger.new(@stderr)
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
35
10
 
11
+ def initialize
36
12
  @hooks = Hash.new { |h,k| h[k] = [] }
37
13
  end
38
14
 
39
- attr_reader :stdout, :stderr
40
- attr_accessor :formatter
41
-
42
15
  # Fire callbacks for the named hook
43
- #
44
16
  def fire(hook, *args)
45
17
  @hooks[hook].each { |t| t.call(*args) }
46
18
  end
47
19
 
48
20
  # Register a callback for a given hook
49
- #
50
21
  def register(hook, obj=nil, &blk)
51
22
  if obj and blk
52
23
  raise "Specify either an object or a block, not both"
@@ -59,79 +30,6 @@ module Puma
59
30
  h
60
31
  end
61
32
 
62
- # Write +str+ to +@stdout+
63
- #
64
- def log(str)
65
- @stdout.puts format(str) if @stdout.respond_to? :puts
66
-
67
- @stdout.flush unless @stdout.sync
68
- rescue Errno::EPIPE
69
- end
70
-
71
- def write(str)
72
- @stdout.write format(str)
73
- end
74
-
75
- def debug(str)
76
- log("% #{str}") if @debug
77
- end
78
-
79
- # Write +str+ to +@stderr+
80
- #
81
- def error(str)
82
- @error_logger.info(text: format("ERROR: #{str}"))
83
- exit 1
84
- end
85
-
86
- def format(str)
87
- formatter.call(str)
88
- end
89
-
90
- # An HTTP connection error has occurred.
91
- # +error+ a connection exception, +req+ the request,
92
- # and +text+ additional info
93
- # @version 5.0.0
94
- #
95
- def connection_error(error, req, text="HTTP connection error")
96
- @error_logger.info(error: error, req: req, text: text)
97
- end
98
-
99
- # An HTTP parse error has occurred.
100
- # +error+ a parsing exception,
101
- # and +req+ the request.
102
- #
103
- def parse_error(error, req)
104
- @error_logger.info(error: error, req: req, text: 'HTTP parse error, malformed request')
105
- end
106
-
107
- # An SSL error has occurred.
108
- # @param error <Puma::MiniSSL::SSLError>
109
- # @param ssl_socket <Puma::MiniSSL::Socket>
110
- #
111
- def ssl_error(error, ssl_socket)
112
- peeraddr = ssl_socket.peeraddr.last rescue "<unknown>"
113
- peercert = ssl_socket.peercert
114
- subject = peercert ? peercert.subject : nil
115
- @error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
116
- end
117
-
118
- # An unknown error has occurred.
119
- # +error+ an exception object, +req+ the request,
120
- # and +text+ additional info
121
- #
122
- def unknown_error(error, req=nil, text="Unknown error")
123
- @error_logger.info(error: error, req: req, text: text)
124
- end
125
-
126
- # Log occurred error debug dump.
127
- # +error+ an exception object, +req+ the request,
128
- # and +text+ additional info
129
- # @version 5.0.0
130
- #
131
- def debug_error(error, req=nil, text="")
132
- @error_logger.debug(error: error, req: req, text: text)
133
- end
134
-
135
33
  def on_booted(&block)
136
34
  register(:on_booted, &block)
137
35
  end
@@ -155,23 +53,5 @@ module Puma
155
53
  def fire_on_stopped!
156
54
  fire(:on_stopped)
157
55
  end
158
-
159
- DEFAULT = new(STDOUT, STDERR)
160
-
161
- # Returns an Events object which writes its status to 2 StringIO
162
- # objects.
163
- #
164
- def self.strings
165
- Events.new StringIO.new, StringIO.new
166
- end
167
-
168
- def self.stdio
169
- Events.new $stdout, $stderr
170
- end
171
-
172
- def self.null
173
- n = NullIO.new
174
- Events.new n, n
175
- end
176
56
  end
177
57
  end
@@ -1,11 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'stringio'
4
+
3
5
  module Puma
4
- class IOBuffer < String
5
- def append(*args)
6
- args.each { |a| concat(a) }
6
+ class IOBuffer < StringIO
7
+ def initialize
8
+ super.binmode
9
+ end
10
+
11
+ def empty?
12
+ length.zero?
7
13
  end
8
14
 
9
- alias reset clear
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
10
45
  end
11
46
  end