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
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,25 +63,58 @@ 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)
67
+
68
+ backlog_str = opts[:backlog] ? "&backlog=#{Integer(opts[:backlog])}" : ''
50
69
 
51
70
  if defined?(JRUBY_VERSION)
52
- ssl_cipher_list = opts[:ssl_cipher_list] ?
53
- "&ssl_cipher_list=#{opts[:ssl_cipher_list]}" : nil
71
+ cipher_suites = opts[:ssl_cipher_list] ? "&ssl_cipher_list=#{opts[:ssl_cipher_list]}" : nil # old name
72
+ cipher_suites = "#{cipher_suites}&cipher_suites=#{opts[:cipher_suites]}" if opts[:cipher_suites]
73
+ protocols = opts[:protocols] ? "&protocols=#{opts[:protocols]}" : nil
54
74
 
55
75
  keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
76
+ keystore_additions = "#{keystore_additions}&keystore-type=#{opts[:keystore_type]}" if opts[:keystore_type]
77
+ if opts[:truststore]
78
+ truststore_additions = "&truststore=#{opts[:truststore]}"
79
+ truststore_additions = "#{truststore_additions}&truststore-pass=#{opts[:truststore_pass]}" if opts[:truststore_pass]
80
+ truststore_additions = "#{truststore_additions}&truststore-type=#{opts[:truststore_type]}" if opts[:truststore_type]
81
+ end
56
82
 
57
- "ssl://#{host}:#{port}?#{keystore_additions}#{ssl_cipher_list}" \
58
- "&verify_mode=#{verify}#{tls_str}#{ca_additions}"
83
+ "ssl://#{host}:#{port}?#{keystore_additions}#{truststore_additions}#{cipher_suites}#{protocols}" \
84
+ "&verify_mode=#{verify}#{tls_str}#{ca_additions}#{backlog_str}"
59
85
  else
60
- ssl_cipher_filter = opts[:ssl_cipher_filter] ?
61
- "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" : nil
62
-
63
- v_flags = (ary = opts[:verification_flags]) ?
64
- "&verification_flags=#{Array(ary).join ','}" : nil
86
+ ssl_cipher_filter = opts[:ssl_cipher_filter] ? "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" : nil
87
+ v_flags = (ary = opts[:verification_flags]) ? "&verification_flags=#{Array(ary).join ','}" : nil
88
+
89
+ cert_flags = (cert = opts[:cert]) ? "cert=#{Puma::Util.escape(cert)}" : nil
90
+ key_flags = (key = opts[:key]) ? "&key=#{Puma::Util.escape(key)}" : nil
91
+
92
+ reuse_flag =
93
+ if (reuse = opts[:reuse])
94
+ if reuse == true
95
+ '&reuse=dflt'
96
+ elsif reuse.is_a?(Hash) && (reuse.key?(:size) || reuse.key?(:timeout))
97
+ val = +''
98
+ if (size = reuse[:size]) && Integer === size
99
+ val << size.to_s
100
+ end
101
+ if (timeout = reuse[:timeout]) && Integer === timeout
102
+ val << ",#{timeout}"
103
+ end
104
+ if val.empty?
105
+ nil
106
+ else
107
+ "&reuse=#{val}"
108
+ end
109
+ else
110
+ nil
111
+ end
112
+ else
113
+ nil
114
+ end
65
115
 
66
- "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}" \
67
- "#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}"
116
+ "ssl://#{host}:#{port}?#{cert_flags}#{key_flags}#{ssl_cipher_filter}" \
117
+ "#{reuse_flag}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}"
68
118
  end
69
119
  end
70
120
 
@@ -100,7 +150,7 @@ module Puma
100
150
  end
101
151
 
102
152
  def default_host
103
- @options[:default_host] || Configuration::DefaultTCPHost
153
+ @options[:default_host] || Configuration::DEFAULTS[:tcp_host]
104
154
  end
105
155
 
106
156
  def inject(&blk)
@@ -191,7 +241,7 @@ module Puma
191
241
  end
192
242
 
193
243
  # Bind the server to +url+. "tcp://", "unix://" and "ssl://" are the only
194
- # accepted protocols. Multiple urls can be bound to, calling `bind` does
244
+ # accepted protocols. Multiple urls can be bound to, calling +bind+ does
195
245
  # not overwrite previous bindings.
196
246
  #
197
247
  # The default is "tcp://0.0.0.0:9292".
@@ -381,6 +431,13 @@ module Puma
381
431
  @options[:rackup] ||= path.to_s
382
432
  end
383
433
 
434
+ # Allows setting `env['rack.url_scheme']`.
435
+ # Only necessary if X-Forwarded-Proto is not being set by your proxy
436
+ # Normal values are 'http' or 'https'.
437
+ def rack_url_scheme(scheme=nil)
438
+ @options[:rack_url_scheme] = scheme
439
+ end
440
+
384
441
  def early_hints(answer=true)
385
442
  @options[:early_hints] = answer
386
443
  end
@@ -429,8 +486,19 @@ module Puma
429
486
  @options[:max_threads] = max
430
487
  end
431
488
 
432
- # Instead of `bind 'ssl://127.0.0.1:9292?key=key_path&cert=cert_path'` you
433
- # can also use the this method.
489
+ # Instead of using +bind+ and manually constructing a URI like:
490
+ #
491
+ # bind 'ssl://127.0.0.1:9292?key=key_path&cert=cert_path'
492
+ #
493
+ # you can use the this method.
494
+ #
495
+ # When binding on localhost you don't need to specify +cert+ and +key+,
496
+ # Puma will assume you are using the +localhost+ gem and try to load the
497
+ # appropriate files.
498
+ #
499
+ # When using the options hash parameter, the `reuse:` value is either
500
+ # `true`, which sets reuse 'on' with default values, or a hash, with `:size`
501
+ # and/or `:timeout` keys, each with integer values.
434
502
  #
435
503
  # @example
436
504
  # ssl_bind '127.0.0.1', '9292', {
@@ -439,15 +507,28 @@ module Puma
439
507
  # ssl_cipher_filter: cipher_filter, # optional
440
508
  # verify_mode: verify_mode, # default 'none'
441
509
  # verification_flags: flags, # optional, not supported by JRuby
510
+ # reuse: true # optional
442
511
  # }
443
- # @example For JRuby, two keys are required: keystore & keystore_pass.
512
+ #
513
+ # @example Using self-signed certificate with the +localhost+ gem:
514
+ # ssl_bind '127.0.0.1', '9292'
515
+ #
516
+ # @example Alternatively, you can provide +cert_pem+ and +key_pem+:
517
+ # ssl_bind '127.0.0.1', '9292', {
518
+ # cert_pem: File.read(path_to_cert),
519
+ # key_pem: File.read(path_to_key),
520
+ # reuse: {size: 2_000, timeout: 20} # optional
521
+ # }
522
+ #
523
+ # @example For JRuby, two keys are required: +keystore+ & +keystore_pass+
444
524
  # ssl_bind '127.0.0.1', '9292', {
445
525
  # keystore: path_to_keystore,
446
526
  # keystore_pass: password,
447
527
  # ssl_cipher_list: cipher_list, # optional
448
528
  # verify_mode: verify_mode # default 'none'
449
529
  # }
450
- def ssl_bind(host, port, opts)
530
+ def ssl_bind(host, port, opts = {})
531
+ add_pem_values_to_options_store(opts)
451
532
  bind self.class.ssl_bind_str(host, port, opts)
452
533
  end
453
534
 
@@ -529,9 +610,8 @@ module Puma
529
610
  # on_worker_boot do
530
611
  # puts 'Before worker boot...'
531
612
  # end
532
- def on_worker_boot(&block)
533
- @options[:before_worker_boot] ||= []
534
- @options[:before_worker_boot] << block
613
+ def on_worker_boot(key = nil, &block)
614
+ process_hook :before_worker_boot, key, block, 'on_worker_boot'
535
615
  end
536
616
 
537
617
  # Code to run immediately before a worker shuts
@@ -546,9 +626,8 @@ module Puma
546
626
  # on_worker_shutdown do
547
627
  # puts 'On worker shutdown...'
548
628
  # end
549
- def on_worker_shutdown(&block)
550
- @options[:before_worker_shutdown] ||= []
551
- @options[:before_worker_shutdown] << block
629
+ def on_worker_shutdown(key = nil, &block)
630
+ process_hook :before_worker_shutdown, key, block, 'on_worker_shutdown'
552
631
  end
553
632
 
554
633
  # Code to run in the master right before a worker is started. The worker's
@@ -562,8 +641,7 @@ module Puma
562
641
  # puts 'Before worker fork...'
563
642
  # end
564
643
  def on_worker_fork(&block)
565
- @options[:before_worker_fork] ||= []
566
- @options[:before_worker_fork] << block
644
+ process_hook :before_worker_fork, nil, block, 'on_worker_fork'
567
645
  end
568
646
 
569
647
  # Code to run in the master after a worker has been started. The worker's
@@ -577,8 +655,7 @@ module Puma
577
655
  # puts 'After worker fork...'
578
656
  # end
579
657
  def after_worker_fork(&block)
580
- @options[:after_worker_fork] ||= []
581
- @options[:after_worker_fork] = block
658
+ process_hook :after_worker_fork, nil, block, 'after_worker_fork'
582
659
  end
583
660
 
584
661
  alias_method :after_worker_boot, :after_worker_fork
@@ -601,9 +678,8 @@ module Puma
601
678
  # end
602
679
  # @version 5.0.0
603
680
  #
604
- def on_refork(&block)
605
- @options[:before_refork] ||= []
606
- @options[:before_refork] << block
681
+ def on_refork(key = nil, &block)
682
+ process_hook :before_refork, key, block, 'on_refork'
607
683
  end
608
684
 
609
685
  # Code to run out-of-band when the worker is idle.
@@ -616,8 +692,7 @@ module Puma
616
692
  #
617
693
  # This can be called multiple times to add several hooks.
618
694
  def out_of_band(&block)
619
- @options[:out_of_band] ||= []
620
- @options[:out_of_band] << block
695
+ process_hook :out_of_band, nil, block, 'out_of_band'
621
696
  end
622
697
 
623
698
  # The directory to operate out of.
@@ -720,6 +795,19 @@ module Puma
720
795
  @options[:tag] = string.to_s
721
796
  end
722
797
 
798
+ # Change the default interval for checking workers.
799
+ #
800
+ # The default value is 5 seconds.
801
+ #
802
+ # @note Cluster mode only.
803
+ # @example
804
+ # worker_check_interval 5
805
+ # @see Puma::Cluster#check_workers
806
+ #
807
+ def worker_check_interval(interval)
808
+ @options[:worker_check_interval] = Integer(interval)
809
+ end
810
+
723
811
  # Verifies that all workers have checked in to the master process within
724
812
  # the given timeout. If not the worker process will be restarted. This is
725
813
  # not a request timeout, it is to protect against a hung or dead process.
@@ -734,7 +822,7 @@ module Puma
734
822
  #
735
823
  def worker_timeout(timeout)
736
824
  timeout = Integer(timeout)
737
- min = Const::WORKER_CHECK_INTERVAL
825
+ min = @options.fetch(:worker_check_interval, Configuration::DEFAULTS[:worker_check_interval])
738
826
 
739
827
  if timeout <= min
740
828
  raise "The minimum worker_timeout must be greater than the worker reporting interval (#{min})"
@@ -766,6 +854,30 @@ module Puma
766
854
  @options[:worker_shutdown_timeout] = Integer(timeout)
767
855
  end
768
856
 
857
+ # Set the strategy for worker culling.
858
+ #
859
+ # There are two possible values:
860
+ #
861
+ # 1. **:youngest** - the youngest workers (i.e. the workers that were
862
+ # the most recently started) will be culled.
863
+ # 2. **:oldest** - the oldest workers (i.e. the workers that were started
864
+ # the longest time ago) will be culled.
865
+ #
866
+ # @note Cluster mode only.
867
+ # @example
868
+ # worker_culling_strategy :oldest
869
+ # @see Puma::Cluster#cull_workers
870
+ #
871
+ def worker_culling_strategy(strategy)
872
+ stategy = strategy.to_sym
873
+
874
+ if ![:youngest, :oldest].include?(strategy)
875
+ raise "Invalid value for worker_culling_strategy - #{stategy}"
876
+ end
877
+
878
+ @options[:worker_culling_strategy] = strategy
879
+ end
880
+
769
881
  # When set to true (the default), workers accept all requests
770
882
  # and queue them before passing them to the handlers.
771
883
  # When set to false, each worker process accepts exactly as
@@ -811,17 +923,23 @@ module Puma
811
923
  # a kernel syscall is required which for very fast rack handlers
812
924
  # slows down the handling significantly.
813
925
  #
814
- # There are 4 possible values:
926
+ # There are 5 possible values:
815
927
  #
816
928
  # 1. **:socket** (the default) - read the peername from the socket using the
817
- # syscall. This is the normal behavior.
929
+ # syscall. This is the normal behavior. If this fails for any reason (e.g.,
930
+ # if the peer disconnects between the connection being accepted and the getpeername
931
+ # system call), Puma will return "0.0.0.0"
818
932
  # 2. **:localhost** - set the remote address to "127.0.0.1"
819
933
  # 3. **header: <http_header>**- set the remote address to the value of the
820
934
  # provided http header. For instance:
821
935
  # `set_remote_address header: "X-Real-IP"`.
822
936
  # Only the first word (as separated by spaces or comma) is used, allowing
823
- # headers such as X-Forwarded-For to be used as well.
824
- # 4. **\<Any string\>** - this allows you to hardcode remote address to any value
937
+ # headers such as X-Forwarded-For to be used as well. If this header is absent,
938
+ # Puma will fall back to the behavior of :socket
939
+ # 4. **proxy_protocol: :v1**- set the remote address to the value read from the
940
+ # HAproxy PROXY protocol, version 1. If the request does not have the PROXY
941
+ # protocol attached to it, will fall back to :socket
942
+ # 5. **\<Any string\>** - this allows you to hardcode remote address to any value
825
943
  # you wish. Because Puma never uses this field anyway, it's format is
826
944
  # entirely in your hands.
827
945
  #
@@ -839,6 +957,13 @@ module Puma
839
957
  if hdr = val[:header]
840
958
  @options[:remote_address] = :header
841
959
  @options[:remote_address_header] = "HTTP_" + hdr.upcase.tr("-", "_")
960
+ elsif protocol_version = val[:proxy_protocol]
961
+ @options[:remote_address] = :proxy_protocol
962
+ protocol_version = protocol_version.downcase.to_sym
963
+ unless [:v1].include?(protocol_version)
964
+ raise "Invalid value for proxy_protocol - #{protocol_version.inspect}"
965
+ end
966
+ @options[:remote_address_proxy_protocol] = protocol_version
842
967
  else
843
968
  raise "Invalid value for set_remote_address - #{val.inspect}"
844
969
  end
@@ -864,23 +989,6 @@ module Puma
864
989
  @options[:fork_worker] = Integer(after_requests)
865
990
  end
866
991
 
867
- # When enabled, Puma will GC 4 times before forking workers.
868
- # If available (Ruby 2.7+), we will also call GC.compact.
869
- # Not recommended for non-MRI Rubies.
870
- #
871
- # Based on the work of Koichi Sasada and Aaron Patterson, this option may
872
- # decrease memory utilization of preload-enabled cluster-mode Pumas. It will
873
- # also increase time to boot and fork. See your logs for details on how much
874
- # time this adds to your boot process. For most apps, it will be less than one
875
- # second.
876
- #
877
- # @see Puma::Cluster#nakayoshi_gc
878
- # @version 5.0.0
879
- #
880
- def nakayoshi_fork(enabled=true)
881
- @options[:nakayoshi_fork] = enabled
882
- end
883
-
884
992
  # The number of requests to attempt inline before sending a client back to
885
993
  # the reactor to be subject to normal ordering.
886
994
  #
@@ -910,5 +1018,36 @@ module Puma
910
1018
  def mutate_stdout_and_stderr_to_sync_on_write(enabled=true)
911
1019
  @options[:mutate_stdout_and_stderr_to_sync_on_write] = enabled
912
1020
  end
1021
+
1022
+ private
1023
+
1024
+ # To avoid adding cert_pem and key_pem as URI params, we store them on the
1025
+ # options[:store] from where Puma binder knows how to find and extract them.
1026
+ def add_pem_values_to_options_store(opts)
1027
+ return if defined?(JRUBY_VERSION)
1028
+
1029
+ @options[:store] ||= []
1030
+
1031
+ # Store cert_pem and key_pem to options[:store] if present
1032
+ [:cert, :key].each do |v|
1033
+ opt_key = :"#{v}_pem"
1034
+ if opts[opt_key]
1035
+ index = @options[:store].length
1036
+ @options[:store] << opts[opt_key]
1037
+ opts[v] = "store:#{index}"
1038
+ end
1039
+ end
1040
+ end
1041
+
1042
+ def process_hook(options_key, key, block, meth)
1043
+ @options[options_key] ||= []
1044
+ if ON_WORKER_KEY.include? key.class
1045
+ @options[options_key] << [block, key.to_sym]
1046
+ elsif key.nil?
1047
+ @options[options_key] << block
1048
+ else
1049
+ raise "'#{method}' key must be String or Symbol"
1050
+ end
1051
+ end
913
1052
  end
914
1053
  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,36 @@
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?
13
+ end
14
+
15
+ def reset
16
+ truncate 0
17
+ rewind
7
18
  end
8
19
 
9
- alias reset clear
20
+ def to_s
21
+ rewind
22
+ read
23
+ end
24
+
25
+ alias_method :clear, :reset
26
+
27
+ # before Ruby 2.5, `write` would only take one argument
28
+ if RUBY_VERSION >= '2.5' && RUBY_ENGINE != 'truffleruby'
29
+ alias_method :append, :write
30
+ else
31
+ def append(*strs)
32
+ strs.each { |str| write str }
33
+ end
34
+ end
10
35
  end
11
36
  end
@@ -16,7 +16,8 @@ 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)
@@ -17,7 +17,7 @@ module Puma
17
17
  # be particularly full-featured or fast. It just has to handle the few places
18
18
  # where Puma relies on JSON serialization internally.
19
19
 
20
- module JSON
20
+ module JSONSerialization
21
21
  QUOTE = /"/
22
22
  BACKSLASH = /\\/
23
23
  CONTROL_CHAR_TO_ESCAPE = /[\x00-\x1F]/ # As required by ECMA-404