puma 5.6.5 → 6.0.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +129 -11
  3. data/README.md +21 -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/testing_benchmarks_local_files.md +150 -0
  9. data/docs/testing_test_rackup_ci_files.md +36 -0
  10. data/ext/puma_http11/extconf.rb +11 -8
  11. data/ext/puma_http11/http11_parser.c +1 -1
  12. data/ext/puma_http11/http11_parser.h +1 -1
  13. data/ext/puma_http11/http11_parser.java.rl +2 -2
  14. data/ext/puma_http11/http11_parser.rl +2 -2
  15. data/ext/puma_http11/http11_parser_common.rl +2 -2
  16. data/ext/puma_http11/mini_ssl.c +36 -15
  17. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  18. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
  19. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +156 -53
  20. data/ext/puma_http11/puma_http11.c +17 -9
  21. data/lib/puma/app/status.rb +3 -3
  22. data/lib/puma/binder.rb +36 -42
  23. data/lib/puma/cli.rb +11 -17
  24. data/lib/puma/client.rb +26 -13
  25. data/lib/puma/cluster/worker.rb +13 -11
  26. data/lib/puma/cluster/worker_handle.rb +4 -1
  27. data/lib/puma/cluster.rb +31 -30
  28. data/lib/puma/configuration.rb +74 -58
  29. data/lib/puma/const.rb +76 -88
  30. data/lib/puma/control_cli.rb +3 -6
  31. data/lib/puma/detect.rb +2 -0
  32. data/lib/puma/dsl.rb +96 -52
  33. data/lib/puma/error_logger.rb +17 -9
  34. data/lib/puma/events.rb +6 -126
  35. data/lib/puma/io_buffer.rb +39 -4
  36. data/lib/puma/jruby_restart.rb +2 -1
  37. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  38. data/lib/puma/launcher.rb +96 -156
  39. data/lib/puma/log_writer.rb +137 -0
  40. data/lib/puma/minissl/context_builder.rb +23 -12
  41. data/lib/puma/minissl.rb +82 -11
  42. data/lib/puma/plugin/tmp_restart.rb +1 -1
  43. data/lib/puma/rack/builder.rb +4 -4
  44. data/lib/puma/rack_default.rb +1 -1
  45. data/lib/puma/reactor.rb +4 -4
  46. data/lib/puma/request.rb +334 -166
  47. data/lib/puma/runner.rb +45 -20
  48. data/lib/puma/server.rb +55 -71
  49. data/lib/puma/single.rb +11 -11
  50. data/lib/puma/state_file.rb +1 -4
  51. data/lib/puma/systemd.rb +3 -2
  52. data/lib/puma/thread_pool.rb +16 -16
  53. data/lib/puma/util.rb +0 -11
  54. data/lib/puma.rb +12 -11
  55. data/lib/rack/handler/puma.rb +9 -9
  56. metadata +7 -3
  57. data/lib/puma/queue_close.rb +0 -26
data/lib/puma/dsl.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/const'
4
- require 'puma/util'
3
+ require_relative 'const'
4
+ require_relative 'util'
5
5
 
6
6
  module Puma
7
7
  # The methods that are available for use inside the configuration file.
@@ -32,8 +32,24 @@ module Puma
32
32
  # You can also find many examples being used by the test suite in
33
33
  # +test/config+.
34
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
+ #
35
51
  class DSL
36
- include ConfigDefault
52
+ ON_WORKER_KEY = [String, Symbol].freeze
37
53
 
38
54
  # convenience method so logic can be used in CI
39
55
  # @see ssl_bind
@@ -52,25 +68,53 @@ module Puma
52
68
  backlog_str = opts[:backlog] ? "&backlog=#{Integer(opts[:backlog])}" : ''
53
69
 
54
70
  if defined?(JRUBY_VERSION)
55
- ssl_cipher_list = opts[:ssl_cipher_list] ?
56
- "&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
57
74
 
58
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
59
82
 
60
- "ssl://#{host}:#{port}?#{keystore_additions}#{ssl_cipher_list}" \
83
+ "ssl://#{host}:#{port}?#{keystore_additions}#{truststore_additions}#{cipher_suites}#{protocols}" \
61
84
  "&verify_mode=#{verify}#{tls_str}#{ca_additions}#{backlog_str}"
62
85
  else
63
- ssl_cipher_filter = opts[:ssl_cipher_filter] ?
64
- "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" : nil
65
-
66
- v_flags = (ary = opts[:verification_flags]) ?
67
- "&verification_flags=#{Array(ary).join ','}" : nil
68
-
69
- cert_flags = (cert = opts[:cert]) ? "cert=#{Puma::Util.escape(opts[:cert])}" : nil
70
- key_flags = (cert = opts[:key]) ? "&key=#{Puma::Util.escape(opts[:key])}" : 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
71
115
 
72
- "ssl://#{host}:#{port}?#{cert_flags}#{key_flags}" \
73
- "#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}"
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}"
74
118
  end
75
119
  end
76
120
 
@@ -106,7 +150,7 @@ module Puma
106
150
  end
107
151
 
108
152
  def default_host
109
- @options[:default_host] || Configuration::DefaultTCPHost
153
+ @options[:default_host] || Configuration::DEFAULTS[:tcp_host]
110
154
  end
111
155
 
112
156
  def inject(&blk)
@@ -206,6 +250,7 @@ module Puma
206
250
  #
207
251
  # * Set the socket backlog depth with +backlog+, default is 1024.
208
252
  # * Set up an SSL certificate with +key+ & +cert+.
253
+ # * Set up an SSL certificate for mTLS with +key+, +cert+, +ca+ and +verify_mode+.
209
254
  # * Set whether to optimize for low latency instead of throughput with
210
255
  # +low_latency+, default is to not optimize for low latency. This is done
211
256
  # via +Socket::TCP_NODELAY+.
@@ -215,6 +260,8 @@ module Puma
215
260
  # bind 'unix:///var/run/puma.sock?backlog=512'
216
261
  # @example SSL cert
217
262
  # bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem'
263
+ # @example SSL cert for mutual TLS (mTLS)
264
+ # bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem&ca=ca.pem&verify_mode=force_peer'
218
265
  # @example Disable optimization for low latency
219
266
  # bind 'tcp://0.0.0.0:9292?low_latency=false'
220
267
  # @example Socket permissions
@@ -452,6 +499,10 @@ module Puma
452
499
  # Puma will assume you are using the +localhost+ gem and try to load the
453
500
  # appropriate files.
454
501
  #
502
+ # When using the options hash parameter, the `reuse:` value is either
503
+ # `true`, which sets reuse 'on' with default values, or a hash, with `:size`
504
+ # and/or `:timeout` keys, each with integer values.
505
+ #
455
506
  # @example
456
507
  # ssl_bind '127.0.0.1', '9292', {
457
508
  # cert: path_to_cert,
@@ -459,6 +510,7 @@ module Puma
459
510
  # ssl_cipher_filter: cipher_filter, # optional
460
511
  # verify_mode: verify_mode, # default 'none'
461
512
  # verification_flags: flags, # optional, not supported by JRuby
513
+ # reuse: true # optional
462
514
  # }
463
515
  #
464
516
  # @example Using self-signed certificate with the +localhost+ gem:
@@ -468,6 +520,7 @@ module Puma
468
520
  # ssl_bind '127.0.0.1', '9292', {
469
521
  # cert_pem: File.read(path_to_cert),
470
522
  # key_pem: File.read(path_to_key),
523
+ # reuse: {size: 2_000, timeout: 20} # optional
471
524
  # }
472
525
  #
473
526
  # @example For JRuby, two keys are required: +keystore+ & +keystore_pass+
@@ -560,9 +613,8 @@ module Puma
560
613
  # on_worker_boot do
561
614
  # puts 'Before worker boot...'
562
615
  # end
563
- def on_worker_boot(&block)
564
- @options[:before_worker_boot] ||= []
565
- @options[:before_worker_boot] << block
616
+ def on_worker_boot(key = nil, &block)
617
+ process_hook :before_worker_boot, key, block, 'on_worker_boot'
566
618
  end
567
619
 
568
620
  # Code to run immediately before a worker shuts
@@ -577,9 +629,8 @@ module Puma
577
629
  # on_worker_shutdown do
578
630
  # puts 'On worker shutdown...'
579
631
  # end
580
- def on_worker_shutdown(&block)
581
- @options[:before_worker_shutdown] ||= []
582
- @options[:before_worker_shutdown] << block
632
+ def on_worker_shutdown(key = nil, &block)
633
+ process_hook :before_worker_shutdown, key, block, 'on_worker_shutdown'
583
634
  end
584
635
 
585
636
  # Code to run in the master right before a worker is started. The worker's
@@ -593,8 +644,7 @@ module Puma
593
644
  # puts 'Before worker fork...'
594
645
  # end
595
646
  def on_worker_fork(&block)
596
- @options[:before_worker_fork] ||= []
597
- @options[:before_worker_fork] << block
647
+ process_hook :before_worker_fork, nil, block, 'on_worker_fork'
598
648
  end
599
649
 
600
650
  # Code to run in the master after a worker has been started. The worker's
@@ -608,8 +658,7 @@ module Puma
608
658
  # puts 'After worker fork...'
609
659
  # end
610
660
  def after_worker_fork(&block)
611
- @options[:after_worker_fork] ||= []
612
- @options[:after_worker_fork] << block
661
+ process_hook :after_worker_fork, nil, block, 'after_worker_fork'
613
662
  end
614
663
 
615
664
  alias_method :after_worker_boot, :after_worker_fork
@@ -632,9 +681,8 @@ module Puma
632
681
  # end
633
682
  # @version 5.0.0
634
683
  #
635
- def on_refork(&block)
636
- @options[:before_refork] ||= []
637
- @options[:before_refork] << block
684
+ def on_refork(key = nil, &block)
685
+ process_hook :before_refork, key, block, 'on_refork'
638
686
  end
639
687
 
640
688
  # Code to run out-of-band when the worker is idle.
@@ -647,8 +695,7 @@ module Puma
647
695
  #
648
696
  # This can be called multiple times to add several hooks.
649
697
  def out_of_band(&block)
650
- @options[:out_of_band] ||= []
651
- @options[:out_of_band] << block
698
+ process_hook :out_of_band, nil, block, 'out_of_band'
652
699
  end
653
700
 
654
701
  # The directory to operate out of.
@@ -778,7 +825,7 @@ module Puma
778
825
  #
779
826
  def worker_timeout(timeout)
780
827
  timeout = Integer(timeout)
781
- min = @options.fetch(:worker_check_interval, Puma::ConfigDefault::DefaultWorkerCheckInterval)
828
+ min = @options.fetch(:worker_check_interval, Configuration::DEFAULTS[:worker_check_interval])
782
829
 
783
830
  if timeout <= min
784
831
  raise "The minimum worker_timeout must be greater than the worker reporting interval (#{min})"
@@ -882,13 +929,16 @@ module Puma
882
929
  # There are 5 possible values:
883
930
  #
884
931
  # 1. **:socket** (the default) - read the peername from the socket using the
885
- # syscall. This is the normal behavior.
932
+ # syscall. This is the normal behavior. If this fails for any reason (e.g.,
933
+ # if the peer disconnects between the connection being accepted and the getpeername
934
+ # system call), Puma will return "0.0.0.0"
886
935
  # 2. **:localhost** - set the remote address to "127.0.0.1"
887
936
  # 3. **header: <http_header>**- set the remote address to the value of the
888
937
  # provided http header. For instance:
889
938
  # `set_remote_address header: "X-Real-IP"`.
890
939
  # Only the first word (as separated by spaces or comma) is used, allowing
891
- # headers such as X-Forwarded-For to be used as well.
940
+ # headers such as X-Forwarded-For to be used as well. If this header is absent,
941
+ # Puma will fall back to the behavior of :socket
892
942
  # 4. **proxy_protocol: :v1**- set the remote address to the value read from the
893
943
  # HAproxy PROXY protocol, version 1. If the request does not have the PROXY
894
944
  # protocol attached to it, will fall back to :socket
@@ -942,23 +992,6 @@ module Puma
942
992
  @options[:fork_worker] = Integer(after_requests)
943
993
  end
944
994
 
945
- # When enabled, Puma will GC 4 times before forking workers.
946
- # If available (Ruby 2.7+), we will also call GC.compact.
947
- # Not recommended for non-MRI Rubies.
948
- #
949
- # Based on the work of Koichi Sasada and Aaron Patterson, this option may
950
- # decrease memory utilization of preload-enabled cluster-mode Pumas. It will
951
- # also increase time to boot and fork. See your logs for details on how much
952
- # time this adds to your boot process. For most apps, it will be less than one
953
- # second.
954
- #
955
- # @see Puma::Cluster#nakayoshi_gc
956
- # @version 5.0.0
957
- #
958
- def nakayoshi_fork(enabled=true)
959
- @options[:nakayoshi_fork] = enabled
960
- end
961
-
962
995
  # The number of requests to attempt inline before sending a client back to
963
996
  # the reactor to be subject to normal ordering.
964
997
  #
@@ -1008,5 +1041,16 @@ module Puma
1008
1041
  end
1009
1042
  end
1010
1043
  end
1044
+
1045
+ def process_hook(options_key, key, block, meth)
1046
+ @options[options_key] ||= []
1047
+ if ON_WORKER_KEY.include? key.class
1048
+ @options[options_key] << [block, key.to_sym]
1049
+ elsif key.nil?
1050
+ @options[options_key] << block
1051
+ else
1052
+ raise "'#{method}' key must be String or Symbol"
1053
+ end
1054
+ end
1011
1055
  end
1012
1056
  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
@@ -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)
@@ -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